package gdsc.smlm.ij.plugins;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.List;
import org.junit.Assert;
import org.junit.Test;
import org.junit.internal.ArrayComparisonFailure;
import gdsc.core.utils.Random;
import gdsc.smlm.function.gaussian.Gaussian2DFunction;
import gdsc.smlm.results.IdPeakResult;
import gdsc.smlm.results.MemoryPeakResults;
import gdsc.smlm.results.PeakResult;
import gdsc.smlm.results.TSFPeakResultsWriter;
import gdsc.smlm.tsf.TaggedSpotFile.FitMode;
import gdsc.smlm.tsf.TaggedSpotFile.FluorophoreType;
import gdsc.smlm.tsf.TaggedSpotFile.IntensityUnits;
import gdsc.smlm.tsf.TaggedSpotFile.LocationUnits;
import gdsc.smlm.tsf.TaggedSpotFile.Spot;
import gdsc.smlm.tsf.TaggedSpotFile.SpotList;
import ij.Macro;
/**
* Test the ResultsManager functionality to load results from file when the file has options.
*/
public class ResultsManagerTest
{
private gdsc.core.utils.Random rand = new Random();
@Test
public void writeTSFMatchesRead()
{
writeTSFMatchesRead(1, 1, 1, 1);
}
@Test
public void writeTSFMatchesReadWithChannels()
{
writeTSFMatchesRead(3, 1, 1, 1);
}
@Test
public void writeTSFMatchesReadWithSlices()
{
writeTSFMatchesRead(1, 3, 1, 1);
}
@Test
public void writeTSFMatchesReadWithPositions()
{
writeTSFMatchesRead(1, 1, 3, 1);
}
@Test
public void writeTSFMatchesReadWithTypes()
{
writeTSFMatchesRead(1, 1, 1, 3);
}
@Test
public void writeTSFMatchesReadWithCombinations()
{
writeTSFMatchesRead(2, 2, 2, 2);
}
private void writeTSFMatchesRead(int channels, int slices, int positions, int types)
{
String filename = createFile();
FileOutputStream out = null;
try
{
out = new FileOutputStream(filename);
}
catch (Exception e)
{
closeOutput(out);
e.printStackTrace();
Assert.fail(e.getMessage());
}
// Write the offsets used in the TSF format
try
{
DataOutputStream dos = new DataOutputStream(out);
dos.writeInt(0);
dos.writeLong(0);
}
catch (IOException e)
{
closeOutput(out);
e.printStackTrace();
Assert.fail(e.getMessage());
}
// Generate random spots
int size = 1000;
Spot[] spots = new Spot[size];
for (int i = 1; i <= size; i++)
{
Spot.Builder builder = Spot.newBuilder();
builder.setChannel(1 + rand.nextInt(channels));
builder.setSlice(1 + rand.nextInt(slices));
builder.setPos(1 + rand.nextInt(positions));
builder.setFluorophoreType(rand.nextInt(1, types));
builder.setMolecule(i); // This is a required field but is ignored when reading
builder.setCluster(rand.nextInt(10));
builder.setFrame(rand.nextInt(1, 100));
builder.setXPosition(rand.nextInt(50));
builder.setYPosition(rand.nextInt(50));
builder.setBackground(rand.next());
builder.setIntensity(rand.next());
builder.setX(rand.next());
builder.setY(rand.next());
builder.setWidth(TSFPeakResultsWriter.SD_TO_FWHM_FACTOR * rand.next());
Spot spot = builder.build();
spots[i - 1] = spot;
try
{
spot.writeDelimitedTo(out);
}
catch (IOException e)
{
closeOutput(out);
e.printStackTrace();
Assert.fail(e.getMessage());
}
}
// Write the header
// Get the offset to the SpotList message
long offset = 0;
try
{
// The offset is the amount to skip forward after reading the int
// magic number (4 bytes) and long offset (8 bytes)
//out.flush();
offset = out.getChannel().position() - 12;
}
catch (IOException e)
{
closeOutput(out);
e.printStackTrace();
Assert.fail(e.getMessage());
}
// Record the SpotList message
SpotList.Builder builder = SpotList.newBuilder();
builder.setApplicationId(1);
builder.setNrSpots(size);
builder.setLocationUnits(LocationUnits.PIXELS);
builder.setIntensityUnits(IntensityUnits.COUNTS);
builder.setFitMode(FitMode.ONEAXIS);
builder.setNrChannels(channels);
builder.setNrSlices(slices);
builder.setNrPos(positions);
for (int type = 1; type <= types; type++)
{
FluorophoreType.Builder typeBuilder = FluorophoreType.newBuilder();
typeBuilder.setId(type);
typeBuilder.setDescription("Type " + type);
typeBuilder.setIsFiducial(rand.next() < 0.5f);
builder.addFluorophoreTypes(typeBuilder.build());
}
SpotList spotList = builder.build();
try
{
spotList.writeDelimitedTo(out);
}
catch (IOException e)
{
e.printStackTrace();
Assert.fail(e.getMessage());
}
finally
{
closeOutput(out);
}
// Write the offset to the SpotList message into the offset position
RandomAccessFile f = null;
try
{
f = new RandomAccessFile(new File(filename), "rw");
f.seek(4);
f.writeLong(offset);
}
catch (Exception e)
{
e.printStackTrace();
Assert.fail(e.getMessage());
}
finally
{
if (f != null)
{
try
{
f.close();
}
catch (IOException e)
{
}
}
}
// Read each combination
for (int channel = 1; channel <= channels; channel++)
for (int slice = 1; slice <= slices; slice++)
for (int position = 1; position <= positions; position++)
for (int type = 1; type <= types; type++)
{
StringBuilder sb = new StringBuilder();
sb.append(" channel=").append(channel);
sb.append(" slice=").append(slice);
sb.append(" position=").append(position);
sb.append(" fluorophore_type=[").append(type).append(":Type ").append(type).append(":fiducial=")
.append(builder.getFluorophoreTypes(type - 1).getIsFiducial()).append(']');
// This is needed to trick the Macro class into returning the options
// for the thread to the GenericDialog used in the ResultsManager
Thread.currentThread().setName("Run$_");
Macro.setOptions(sb.toString());
ResultsManager.setInputFilename(filename);
MemoryPeakResults in = ResultsManager.loadInputResults(ResultsManager.INPUT_FILE, false);
checkEqual(spots, channel, slice, position, type, in);
}
}
private void closeOutput(FileOutputStream out)
{
if (out == null)
return;
try
{
out.close();
}
catch (Exception e)
{
// Ignore exception
}
finally
{
out = null;
}
}
private void checkEqual(Spot[] spots, int channel, int slice, int position, int type,
MemoryPeakResults actualResults) throws ArrayComparisonFailure
{
Assert.assertNotNull("Input results are null", actualResults);
MemoryPeakResults expectedResults = extract(spots, channel, slice, position, type);
Assert.assertEquals("Size differ", expectedResults.size(), actualResults.size());
final float delta = 0;
List<PeakResult> expected = expectedResults.getResults();
List<PeakResult> actual = actualResults.getResults();
for (int i = 0; i < actualResults.size(); i++)
{
PeakResult p1 = expected.get(i);
PeakResult p2 = actual.get(i);
Assert.assertEquals("Peak mismatch @ " + i, p1.getFrame(), p2.getFrame());
Assert.assertEquals("Orig X mismatch @ " + i, p1.origX, p2.origX);
Assert.assertEquals("Orig Y mismatch @ " + i, p1.origY, p2.origY);
Assert.assertEquals("Orig value mismatch @ " + i, p1.origValue, p2.origValue, delta);
Assert.assertEquals("Error mismatch @ " + i, p1.error, p2.error, 1e-6);
Assert.assertEquals("Noise mismatch @ " + i, p1.noise, p2.noise, delta);
Assert.assertNotNull("Params is null @ " + i, p2.params);
Assert.assertEquals("Background mismatch @ " + i, p1.getBackground(), p2.getBackground(), delta);
Assert.assertEquals("Signal mismatch @ " + i, p1.getSignal(), p2.getSignal(), delta);
Assert.assertEquals("XPosition mismatch @ " + i, p1.getXPosition(), p2.getXPosition(), delta);
Assert.assertEquals("YPosition mismatch @ " + i, p1.getYPosition(), p2.getYPosition(), delta);
Assert.assertEquals("XSD mismatch @ " + i, p1.getXSD(), p2.getXSD(), 1e-6);
Assert.assertEquals("YSD mismatch @ " + i, p1.getYSD(), p2.getYSD(), 1e-6);
Assert.assertEquals("ID mismatch @ " + i, p1.getId(), p2.getId());
}
}
private MemoryPeakResults extract(Spot[] spots, int channel, int slice, int position, int type)
{
MemoryPeakResults results = new MemoryPeakResults();
for (Spot spot : spots)
{
if (spot.getChannel() == channel && spot.getSlice() == slice && spot.getPos() == position &&
spot.getFluorophoreType() == type)
{
int id = spot.getCluster();
int startFrame = spot.getFrame();
int origX = spot.getXPosition();
int origY = spot.getYPosition();
float origValue = 0;
double error = 0;
float noise = 0;
float[] params = new float[7];
params[Gaussian2DFunction.BACKGROUND] = spot.getBackground();
params[Gaussian2DFunction.SIGNAL] = spot.getIntensity();
params[Gaussian2DFunction.X_POSITION] = spot.getX();
params[Gaussian2DFunction.Y_POSITION] = spot.getY();
params[Gaussian2DFunction.X_SD] = params[Gaussian2DFunction.Y_SD] = spot.getWidth() /
TSFPeakResultsWriter.SD_TO_FWHM_FACTOR;
float[] paramsStdDev = null;
IdPeakResult peak = new IdPeakResult(startFrame, origX, origY, origValue, error, noise, params,
paramsStdDev, id);
results.add(peak);
}
}
return results;
}
private String createFile()
{
File file;
try
{
file = File.createTempFile("test", null);
file.deleteOnExit();
String filename = file.getPath();
return filename;
}
catch (IOException e)
{
Assert.fail("Cannot create temp files for IO testing");
}
return null; // Allow compilation but the assert will stop the code
}
}