package gdsc.smlm.results;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.InputMismatchException;
import java.util.NoSuchElementException;
import java.util.Scanner;
import gdsc.core.ij.Utils;
import gdsc.core.utils.Maths;
/*-----------------------------------------------------------------------------
* GDSC SMLM Software
*
* Copyright (C) 2016 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.
*---------------------------------------------------------------------------*/
import gdsc.smlm.function.gaussian.Gaussian2DFunction;
/**
* Saves the fit results to file using the simple MALK file format (Molecular Accuracy Localisation Keep). This consists
* of [X,Y,T,Signal] data in a white-space separated format. Comments are allowed with the # character.
*/
public class MALKFilePeakResults extends FilePeakResults
{
private float nmPerPixel;
public MALKFilePeakResults(String filename)
{
super(filename);
}
/*
* (non-Javadoc)
*
* @see gdsc.smlm.results.FilePeakResults#getHeaderEnd()
*/
protected String getHeaderEnd()
{
return null;
}
@Override
protected String getVersion()
{
return "MALK";
}
/*
* (non-Javadoc)
*
* @see gdsc.smlm.results.FilePeakResults#getHeaderComments()
*/
protected String[] getHeaderComments()
{
String[] comments = new String[3];
int count = 0;
nmPerPixel = 1;
if (calibration != null)
{
if (Maths.isFinite(calibration.getNmPerPixel()) && calibration.getNmPerPixel() > 0)
{
nmPerPixel = (float) calibration.getNmPerPixel();
comments[count++] = String.format("Pixel pitch %s (nm)", Utils.rounded(calibration.getNmPerPixel()));
}
if (Maths.isFinite(calibration.getGain()) && calibration.getGain() > 0)
{
comments[count++] = String.format("Gain %s (Count/photon)", Utils.rounded(calibration.getGain()));
}
if (Maths.isFinite(calibration.getExposureTime()) && calibration.getExposureTime() > 0)
{
comments[count++] = String.format("Exposure time %s (seconds)", Utils.rounded(calibration.getExposureTime()*1e-3));
}
}
return Arrays.copyOf(comments, count);
}
/*
* (non-Javadoc)
*
* @see gdsc.smlm.results.FilePeakResults#getFieldNames()
*/
protected String[] getFieldNames()
{
return new String[] { "X", "Y", peakIdColumnName, "Signal" };
}
/*
* (non-Javadoc)
*
* @see gdsc.utils.fitting.results.PeakResults#add(int, int, int, float, double, float, float[], float[])
*/
public void add(int peak, int origX, int origY, float origValue, double error, float noise, float[] params,
float[] paramsStdDev)
{
if (out == null)
return;
StringBuilder sb = new StringBuilder(100);
addStandardData(sb, params[Gaussian2DFunction.X_POSITION], params[Gaussian2DFunction.Y_POSITION], peak,
params[Gaussian2DFunction.SIGNAL]);
writeResult(1, sb.toString());
}
private void addStandardData(StringBuilder sb, final float x, final float y, final int frame, final float signal)
{
sb.append(x * nmPerPixel);
sb.append('\t');
sb.append(y * nmPerPixel);
sb.append('\t');
sb.append(frame);
sb.append('\t');
sb.append(signal);
sb.append('\n');
}
public void addAll(Collection<PeakResult> results)
{
if (out == null)
return;
int count = 0;
StringBuilder sb = new StringBuilder(2000);
for (PeakResult result : results)
{
// Add the standard data
addStandardData(sb, result.getXPosition(), result.getYPosition(), result.getFrame(), result.getSignal());
// Flush the output to allow for very large input lists
if (++count >= 20)
{
writeResult(count, sb.toString());
if (!isActive())
return;
sb.setLength(0);
count = 0;
}
}
writeResult(count, sb.toString());
}
/**
* Output a cluster to the results file.
* <p>
* Note: This is not synchronised
*
* @param cluster
*/
public void addCluster(Cluster cluster)
{
if (out == null)
return;
if (cluster.size() > 0)
{
float[] centroid = cluster.getCentroid();
writeResult(0, String.format("#Cluster %f %f (+/-%f) n=%d\n", centroid[0], centroid[1],
cluster.getStandardDeviation(), cluster.size()));
addAll(cluster);
}
}
private void addAll(Cluster cluster)
{
addAll(cluster.getPoints());
}
/**
* Output a trace to the results file.
* <p>
* Note: This is not synchronised
*
* @param trace
*/
public void addTrace(Trace trace)
{
if (out == null)
return;
if (trace.size() > 0)
{
float[] centroid = trace.getCentroid();
writeResult(0,
String.format("#Trace %f %f (+/-%f) n=%d, b=%d, on=%f, off=%f, signal= %f\n", centroid[0],
centroid[1], trace.getStandardDeviation(), trace.size(), trace.getNBlinks(),
trace.getOnTime(), trace.getOffTime(), trace.getSignal()));
addAll(trace);
}
}
/*
* (non-Javadoc)
*
* @see gdsc.utils.fitting.PeakResults#end()
*/
public void end()
{
if (out == null)
return;
// Close the file.
try
{
closeOutput();
if (!isSortAfterEnd())
return;
ArrayList<Result> results = new ArrayList<Result>(size);
StringBuffer header = new StringBuffer();
BufferedReader input = new BufferedReader(new FileReader(filename));
try
{
String line;
// Skip the header
while ((line = input.readLine()) != null)
{
if (line.charAt(0) != '#')
{
// This is the first record
results.add(new Result(line));
break;
}
else
header.append(line).append("\n");
}
while ((line = input.readLine()) != null)
{
results.add(new Result(line));
}
}
finally
{
input.close();
}
Collections.sort(results);
BufferedWriter output = new BufferedWriter(new FileWriter(filename));
try
{
output.write(header.toString());
for (Result result : results)
{
output.write(result.line);
output.write("\n");
}
}
finally
{
output.close();
}
}
catch (IOException e)
{
// ignore
}
finally
{
out = null;
}
}
private class Result implements Comparable<Result>
{
String line;
int slice = 0;
public Result(String line)
{
this.line = line;
extractSlice();
}
private void extractSlice()
{
Scanner scanner = new Scanner(line);
scanner.useDelimiter("\t");
try
{
scanner.nextFloat(); // X
scanner.nextFloat(); // Y
slice = scanner.nextInt();
scanner.close();
}
catch (InputMismatchException e)
{
}
catch (NoSuchElementException e)
{
}
}
public int compareTo(Result o)
{
// Sort by slice number
// (Note: peak height is already done in the run(...) method)
return slice - o.slice;
}
}
}