package gdsc.smlm.results;
import gdsc.core.utils.Random;
import gdsc.smlm.function.gaussian.Gaussian2DFunction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import org.junit.Assert;
import org.junit.Test;
public class TraceManagerTest
{
@Test
public void canTraceSinglePulseWithFixedCoords()
{
Random rand = new Random(30051977);
float[] params = createParams(rand);
Trace trace = new Trace();
for (int i = 0; i < 5; i++)
{
trace.add(new PeakResult(i, 0, 0, 0, 0, 0, params, null));
}
runTracing(0, 1, trace);
}
@Test
public void canTraceSinglePulseWithMovingCoords()
{
Random rand = new Random(30051977);
float distance = 0.5f;
float[] params = createParams(rand);
Trace trace = new Trace();
for (int i = 0; i < 5; i++)
{
move(rand, params, distance);
trace.add(new PeakResult(i, 0, 0, 0, 0, 0, params, null));
}
runTracing(distance, 1, trace);
}
@Test
public void canTraceMultiplePulseWithFixedCoords()
{
Random rand = new Random(30051977);
int maxOffTime = 5;
float[] params = createParams(rand);
Trace trace = new Trace();
for (int i = 0; i < 5; i++)
{
trace.add(new PeakResult(i, 0, 0, 0, 0, 0, params, null));
}
for (int i = 0; i < 5; i++)
{
trace.add(new PeakResult(i + maxOffTime, 0, 0, 0, 0, 0, params, null));
}
runTracing(0, maxOffTime + 1, trace);
}
@Test
public void canTraceMultiplePulseWithMovingCoords()
{
Random rand = new Random(30051977);
float distance = 0.5f;
int maxOffTime = 5;
float[] params = createParams(rand);
Trace trace = new Trace();
for (int i = 0; i < 5; i++)
{
move(rand, params, distance);
trace.add(new PeakResult(i, 0, 0, 0, 0, 0, params, null));
}
for (int i = 0; i < 5; i++)
{
move(rand, params, distance);
trace.add(new PeakResult(i + maxOffTime, 0, 0, 0, 0, 0, params, null));
}
runTracing(distance, maxOffTime + 1, trace);
}
private void runTracing(double d, int t, Trace... expected)
{
MemoryPeakResults results = toPeakResults(expected);
TraceManager tm = new TraceManager(results);
int n = tm.traceMolecules(d, t);
Assert.assertEquals("Incorrect number of traces", expected.length, n);
Trace[] actual = tm.getTraces();
areEqual(expected, actual);
}
@Test
public void canTraceMultipleFluorophoresWithFixedCoords()
{
simulate(new Random(), 1000, 1, 5, 2, 0);
}
@Test
public void canTraceMultiplePulsingFluorophoresWithFixedCoords()
{
simulate(new Random(), 1000, 5, 5, 10, 0);
}
@Test
public void canTraceMultipleFluorophoresWithMovingCoords()
{
// This test can fail if the moving fluorophores paths intersect
// so we used a known seed that is ok
simulate(new Random(3), 1000, 1, 5, 2, 0.5f);
}
@Test
public void canTraceMultiplePulsingFluorophoresWithMovingCoords()
{
// This test can fail if the moving fluorophores paths intersect
// so we used a known seed that is ok
simulate(new Random(3005), 100, 5, 5, 10, 0.5f);
}
private void simulate(Random rand, int molecules, int maxPulses, int maxOnTime, int maxOffTime, float distance)
{
Trace[] expected = new Trace[molecules];
for (int j = 0; j < expected.length; j++)
{
float[] params = createParams(rand);
int t = rand.nextInt(1, 200);
Trace trace = new Trace();
int pulses = rand.nextInt(1, maxPulses);
for (int p = 0; p < pulses; p++)
{
int length = rand.nextInt(1, maxOnTime);
for (int i = 0; i < length; i++)
{
move(rand, params, distance);
trace.add(new PeakResult(t++, 0, 0, 0, 0, 0, params, null));
}
t += rand.nextInt(1, maxOffTime);
}
expected[j] = trace;
}
double d = (distance > 0) ? Math.sqrt(2.05 * distance * distance) : 0;
runTracing(d, maxOffTime + 1, expected);
}
private static float[] createParams(Random rand)
{
float[] params = new float[7];
params[Gaussian2DFunction.X_POSITION] = rand.next() * 256f;
params[Gaussian2DFunction.Y_POSITION] = rand.next() * 256f;
return params;
}
private MemoryPeakResults toPeakResults(Trace... traces)
{
MemoryPeakResults results = new MemoryPeakResults();
for (Trace t : traces)
{
results.addAll(t.getPoints());
}
Collections.shuffle(results.getResults());
return results;
}
private void areEqual(Trace[] expected, Trace[] actual)
{
Assert.assertNotNull(expected);
Assert.assertNotNull(actual);
Assert.assertEquals("Traces are different lengths", expected.length, actual.length);
sort(expected);
sort(actual);
for (int i = 0; i < expected.length; i++)
{
ArrayList<PeakResult> e = expected[i].getPoints();
ArrayList<PeakResult> a = actual[i].getPoints();
Assert.assertEquals("Points are different lengths [" + i + "]", e.size(), a.size());
for (int j = 0; j < e.size(); j++)
{
PeakResult p1 = e.get(j);
PeakResult p2 = a.get(j);
Assert.assertEquals("Frames different", p1.getFrame(), p2.getFrame());
Assert.assertEquals("X different", p1.getXPosition(), p2.getXPosition(), 1e-3f);
Assert.assertEquals("Y different", p1.getYPosition(), p2.getYPosition(), 1e-3f);
}
}
}
private void sort(Trace[] traces)
{
Arrays.sort(traces, new Comparator<Trace>()
{
public int compare(Trace o1, Trace o2)
{
PeakResult p1 = o1.getHead();
PeakResult p2 = o2.getHead();
int result = p1.getFrame() - p2.getFrame();
if (result != 0)
return result;
if (p1.getXPosition() < p2.getXPosition())
return -1;
if (p1.getXPosition() > p2.getXPosition())
return 1;
if (p1.getYPosition() < p2.getYPosition())
return -1;
if (p1.getYPosition() > p2.getYPosition())
return 1;
return 0;
}
});
}
private static void move(Random rand, float[] params, float distance)
{
if (distance > 0)
{
params[Gaussian2DFunction.X_POSITION] += -distance + rand.next() * 2 * distance;
params[Gaussian2DFunction.Y_POSITION] += -distance + rand.next() * 2 * distance;
}
}
}