package gdsc.smlm.ij.plugins;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.math3.random.RandomGenerator;
import org.apache.commons.math3.random.Well19937c;
import org.junit.Assert;
import org.junit.Test;
import gdsc.core.utils.DoubleEquality;
import gdsc.core.utils.Statistics;
import gdsc.core.utils.StoredDataStatistics;
import gdsc.smlm.function.gaussian.Gaussian2DFunction;
import gdsc.smlm.model.ActivationEnergyImageModel;
import gdsc.smlm.model.CompoundMoleculeModel;
import gdsc.smlm.model.DiffusionType;
import gdsc.smlm.model.FluorophoreSequenceModel;
import gdsc.smlm.model.ImageModel;
import gdsc.smlm.model.LocalisationModel;
import gdsc.smlm.model.MoleculeModel;
import gdsc.smlm.model.SpatialDistribution;
import gdsc.smlm.model.SpatialIllumination;
import gdsc.smlm.model.UniformDistribution;
import gdsc.smlm.model.UniformIllumination;
import gdsc.smlm.results.Calibration;
import gdsc.smlm.results.MemoryPeakResults;
import gnu.trove.procedure.TIntProcedure;
import gnu.trove.set.hash.TIntHashSet;
public class BlinkEstimatorTest
{
private RandomGenerator rand = new Well19937c(System.currentTimeMillis() + System.identityHashCode(this));
// Set to sensible simulation parameters
double diffusionRate = 0.25; // pixels^2/sec
double pixelPitch = 107;
int msPerFrame = 1000;
double photons = 1000;
float psfWidth = 1.2f;
final double relativeError = 0.2;
final double minPhotons = 20;
final double pDelete = 0;
final double pAdd = 0;
int LOW = 0;
int MEDIUM = 1;
int HIGH = 2;
int MIN_FITTED_POINTS = 5;
int MAX_FITTED_POINTS = 15;
double[] nBlinks = { 0.5, 1.5, 4 };
double[] tOn = { 1.5, 3, 8 };
double[] tOff = { 2.5, 5, 10 };
// If true then test against the real population statistics.
// If false then test against the sampled statistics (i.e. using integer frames).
// Note: When false the success rate is very low so the estimation method does actually
// account for the integer frame sampling and get the population statistics.
boolean usePopulationStatistics = true;
@Test
public void canEstimateBlinkingFromSimulationWithLowNBlinksAndMediumOnOffTimesWithFixedMolecules()
{
int particles = 1000;
double fixedFraction = 1;
estimateBlinking(nBlinks[LOW], tOn[MEDIUM], tOff[MEDIUM], particles, fixedFraction, false, true);
}
@Test
public void canEstimateBlinkingFromSimulationWithMediumNBlinksAndMediumOnOffTimesWithFixedMolecules()
{
int particles = 1000;
double fixedFraction = 1;
estimateBlinking(nBlinks[MEDIUM], tOn[MEDIUM], tOff[MEDIUM], particles, fixedFraction, false, true);
}
@Test
public void canEstimateBlinkingFromSimulationWithHighNBlinksAndMediumOnOffTimesWithFixedMolecules()
{
int particles = 1000;
double fixedFraction = 1;
estimateBlinking(nBlinks[HIGH], tOn[MEDIUM], tOff[MEDIUM], particles, fixedFraction, false, true);
}
@Test
public void canEstimateBlinkingFromSimulationWithLowNBlinksAndHighOnOffTimesWithFixedMolecules()
{
int particles = 1000;
double fixedFraction = 1;
estimateBlinking(nBlinks[LOW], tOn[HIGH], tOff[HIGH], particles, fixedFraction, false, true);
}
@Test
public void canEstimateBlinkingFromSimulationWithMediumNBlinksAndHighOnOffTimesWithFixedMolecules()
{
int particles = 1000;
double fixedFraction = 1;
estimateBlinking(nBlinks[MEDIUM], tOn[HIGH], tOff[HIGH], particles, fixedFraction, false, true);
}
@Test
public void canEstimateBlinkingFromSimulationWithHighNBlinksAndHighOnOffTimesWithFixedMolecules()
{
int particles = 1000;
double fixedFraction = 1;
estimateBlinking(nBlinks[HIGH], tOn[HIGH], tOff[HIGH], particles, fixedFraction, false, true);
}
@Test
public void canEstimateBlinkingFromSimulationWithLowNBlinksAndLowOnOffTimesWithFixedMolecules()
{
int particles = 1000;
double fixedFraction = 1;
estimateBlinking(nBlinks[LOW], tOn[LOW], tOff[LOW], particles, fixedFraction, false, true);
}
@Test
public void canEstimateBlinkingFromSimulationWithMediumNBlinksAndLowOnOffTimesWithFixedMolecules()
{
int particles = 1000;
double fixedFraction = 1;
estimateBlinking(nBlinks[MEDIUM], tOn[LOW], tOff[LOW], particles, fixedFraction, false, true);
}
@Test
public void canEstimateBlinkingFromSimulationWithHighNBlinksAndLowOnOffTimesWithFixedMolecules()
{
int particles = 1000;
double fixedFraction = 1;
estimateBlinking(nBlinks[HIGH], tOn[LOW], tOff[LOW], particles, fixedFraction, false, true);
}
@Test
public void findOptimalFittedPoints()
{
int particles = 1000;
double fixedFraction = 1;
for (boolean timeAtLowerBound : new boolean[] { false })
{
final int[] count = new int[MAX_FITTED_POINTS + 1];
int tests = 0;
for (int run = 0; run < 3; run++)
{
for (double n : nBlinks)
{
for (int i = 0; i < tOn.length; i++)
{
tests++;
TIntHashSet ok = estimateBlinking(n, tOn[i], tOff[i], particles, fixedFraction,
timeAtLowerBound, false);
ok.forEach(new TIntProcedure()
{
public boolean execute(int value)
{
count[value]++;
return true;
}
});
}
}
}
System.out.printf("Time@LowerBound = %b\n", timeAtLowerBound);
for (int nFittedPoints = MIN_FITTED_POINTS; nFittedPoints <= MAX_FITTED_POINTS; nFittedPoints++)
{
System.out.printf("%2d = %2d/%2d |", nFittedPoints, count[nFittedPoints], tests);
for (int i = 0; i < count[nFittedPoints]; i++)
System.out.printf("-");
System.out.printf("\n");
}
}
}
private TIntHashSet estimateBlinking(double nBlinks, double tOn, double tOff, int particles,
double fixedFraction, boolean timeAtLowerBound, boolean doAssert)
{
SpatialIllumination activationIllumination = new UniformIllumination(100);
int totalSteps = 100;
double eAct = totalSteps * 0.3 * activationIllumination.getAveragePhotons();
ImageModel imageModel = new ActivationEnergyImageModel(eAct, activationIllumination, tOn, 0, tOff, 0, nBlinks);
imageModel.setRandomGenerator(rand);
double[] max = new double[] { 256, 256, 32 };
double[] min = new double[3];
SpatialDistribution distribution = new UniformDistribution(min, max, rand.nextInt());
List<CompoundMoleculeModel> compounds = new ArrayList<CompoundMoleculeModel>(1);
CompoundMoleculeModel c = new CompoundMoleculeModel(1, 0, 0, 0, Arrays.asList(new MoleculeModel(0, 0, 0, 0)));
c.setDiffusionRate(diffusionRate);
c.setDiffusionType(DiffusionType.RANDOM_WALK);
compounds.add(c);
List<CompoundMoleculeModel> molecules = imageModel.createMolecules(compounds, particles, distribution, false);
// Activate fluorophores
List<? extends FluorophoreSequenceModel> fluorophores = imageModel.createFluorophores(molecules, totalSteps);
totalSteps = checkTotalSteps(totalSteps, fluorophores);
List<LocalisationModel> localisations = imageModel.createImage(molecules, fixedFraction, totalSteps, photons,
0.5, false);
// // Remove localisations to simulate missed counts.
// List<LocalisationModel> newLocalisations = new ArrayList<LocalisationModel>(localisations.size());
// boolean[] id = new boolean[fluorophores.size() + 1];
// Statistics photonStats = new Statistics();
// for (LocalisationModel l : localisations)
// {
// photonStats.add(l.getIntensity());
// // Remove by intensity threshold and optionally at random.
// if (l.getIntensity() < minPhotons || rand.nextDouble() < pDelete)
// continue;
// newLocalisations.add(l);
// id[l.getId()] = true;
// }
// localisations = newLocalisations;
// System.out.printf("Photons = %f\n", photonStats.getMean());
//
// List<FluorophoreSequenceModel> newFluorophores = new ArrayList<FluorophoreSequenceModel>(fluorophores.size());
// for (FluorophoreSequenceModel f : fluorophores)
// {
// if (id[f.getId()])
// newFluorophores.add(f);
// }
// fluorophores = newFluorophores;
MemoryPeakResults results = new MemoryPeakResults();
results.setCalibration(new Calibration(pixelPitch, 1, msPerFrame));
for (LocalisationModel l : localisations)
{
// Remove by intensity threshold and optionally at random.
if (l.getIntensity() < minPhotons || rand.nextDouble() < pDelete)
continue;
float[] params = new float[7];
params[Gaussian2DFunction.X_POSITION] = (float) l.getX();
params[Gaussian2DFunction.Y_POSITION] = (float) l.getY();
params[Gaussian2DFunction.X_SD] = params[Gaussian2DFunction.Y_SD] = psfWidth;
params[Gaussian2DFunction.SIGNAL] = (float) (l.getIntensity());
results.addf(l.getTime(), 0, 0, 0, 0, 0, params, null);
}
// Add random localisations
for (int i = (int) (localisations.size() * pAdd); i-- > 0;)
{
float[] params = new float[7];
params[Gaussian2DFunction.X_POSITION] = (float) (rand.nextDouble() * max[0]);
params[Gaussian2DFunction.Y_POSITION] = (float) (rand.nextDouble() * max[1]);
params[Gaussian2DFunction.X_SD] = params[Gaussian2DFunction.Y_SD] = psfWidth;
// Intensity doesn't matter at the moment for tracing
params[Gaussian2DFunction.SIGNAL] = (float) (photons);
results.addf(1 + rand.nextInt(totalSteps), 0, 0, 0, 0, 0, params, null);
}
// Get actual simulated stats ...
Statistics statsNBlinks = new Statistics();
Statistics statsTOn = new Statistics();
Statistics statsTOff = new Statistics();
Statistics statsSampledNBlinks = new Statistics();
Statistics statsSampledTOn = new Statistics();
StoredDataStatistics statsSampledTOff = new StoredDataStatistics();
for (FluorophoreSequenceModel f : fluorophores)
{
statsNBlinks.add(f.getNumberOfBlinks());
statsTOn.add(f.getOnTimes());
statsTOff.add(f.getOffTimes());
int[] on = f.getSampledOnTimes();
statsSampledNBlinks.add(on.length);
statsSampledTOn.add(on);
statsSampledTOff.add(f.getSampledOffTimes());
}
System.out.printf("N = %d (%d), N-blinks = %f, tOn = %f, tOff = %f, Fixed = %f\n", fluorophores.size(),
localisations.size(), nBlinks, tOn, tOff, fixedFraction);
System.out.printf("Actual N-blinks = %f (%f), tOn = %f (%f), tOff = %f (%f), 95%% = %f, max = %f\n",
statsNBlinks.getMean(), statsSampledNBlinks.getMean(), statsTOn.getMean(), statsSampledTOn.getMean(),
statsTOff.getMean(), statsSampledTOff.getMean(), statsSampledTOff.getStatistics().getPercentile(95),
statsSampledTOff.getStatistics().getMax());
System.out.printf("-=-=--=-\n");
BlinkEstimator be = new BlinkEstimator();
be.maxDarkTime = (int) (tOff * 10);
be.msPerFrame = msPerFrame;
be.relativeDistance = false;
double d = ImageModel.getRandomMoveDistance(diffusionRate);
be.searchDistance = (fixedFraction < 1) ? Math.sqrt(2 * d * d) * 3 : 0;
be.timeAtLowerBound = timeAtLowerBound;
be.showPlots = false;
//Assert.assertTrue("Max dark time must exceed the dark time of the data (otherwise no plateau)",
// be.maxDarkTime > statsSampledTOff.getStatistics().getMax());
int nMolecules = fluorophores.size();
if (usePopulationStatistics)
{
nBlinks = statsNBlinks.getMean();
tOff = statsTOff.getMean();
}
else
{
nBlinks = statsSampledNBlinks.getMean();
tOff = statsSampledTOff.getMean();
}
// See if any fitting regime gets a correct answer
TIntHashSet ok = new TIntHashSet();
for (int nFittedPoints = MIN_FITTED_POINTS; nFittedPoints <= MAX_FITTED_POINTS; nFittedPoints++)
{
be.nFittedPoints = nFittedPoints;
be.computeBlinkingRate(results, true);
double moleculesError = DoubleEquality.relativeError(nMolecules, be.getNMolecules());
double blinksError = DoubleEquality.relativeError(nBlinks, be.getNBlinks());
double offError = DoubleEquality.relativeError(tOff * msPerFrame, be.getTOff());
System.out.printf("Error %d: N = %f, blinks = %f, tOff = %f : %f\n", nFittedPoints, moleculesError,
blinksError, offError, (moleculesError + blinksError + offError) / 3);
if (moleculesError < relativeError && blinksError < relativeError && offError < relativeError)
{
ok.add(nFittedPoints);
System.out.printf("-=-=--=-\n");
System.out.printf("*** Correct at %d fitted points ***\n", nFittedPoints);
if (doAssert)
break;
}
//if (!be.isIncreaseNFittedPoints())
// break;
}
System.out.printf("-=-=--=-\n");
if (doAssert)
Assert.assertFalse(ok.isEmpty());
//Assert.assertEquals("Invalid N-blinks", nBlinks, be.getNBlinks(), nBlinks * relativeError);
//Assert.assertEquals("Invalid N-molecules", fluorophores.size(), be.getNMolecules(), fluorophores.size() * relativeError);
//Assert.assertEquals("Invalid t-off", tOff * msPerFrame, be.getTOff(), tOff * msPerFrame * relativeError);
return ok;
}
private int checkTotalSteps(int totalSteps, List<? extends FluorophoreSequenceModel> fluorophores)
{
for (FluorophoreSequenceModel f : fluorophores)
{
if (totalSteps < f.getEndTime())
totalSteps = (int) (f.getEndTime() + 1);
}
return totalSteps;
}
}