package gdsc.smlm.results;
/*-----------------------------------------------------------------------------
* GDSC SMLM Software
*
* Copyright (C) 2013 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 java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
/**
* Saves the fit results to a binary file format
*/
public class BinaryFilePeakResults extends FilePeakResults
{
public static final String END_HEADER = "END_HEADER";
// Only write to a single results file
// This uses a separate output from the inherited FilePeakResults
private OutputStream out = null;
public BinaryFilePeakResults(String filename)
{
super(filename);
}
public BinaryFilePeakResults(String filename, boolean showDeviations)
{
super(filename, showDeviations);
}
public BinaryFilePeakResults(String resultsFilename, boolean showDeviations, boolean showEndFrame)
{
super(resultsFilename, showDeviations, showEndFrame);
}
public BinaryFilePeakResults(String resultsFilename, boolean showDeviations, boolean showEndFrame, boolean showId)
{
super(resultsFilename, showDeviations, showEndFrame, showId);
}
/*
* (non-Javadoc)
*
* @see gdsc.utils.fitting.PeakResults#begin()
*/
public void begin()
{
out = null;
size = 0;
try
{
out = new FileOutputStream(filename);
out.write(createResultsHeader().getBytes());
}
catch (Exception e)
{
// TODO - Add better handling of errors
e.printStackTrace();
closeOutput();
}
}
/*
* (non-Javadoc)
*
* @see gdsc.smlm.results.FilePeakResults#getHeaderEnd()
*/
protected String getHeaderEnd()
{
return END_HEADER;
}
/*
* (non-Javadoc)
*
* @see gdsc.smlm.results.FilePeakResults#getHeaderComments()
*/
protected String[] getHeaderComments()
{
String[] comments = new String[2];
comments[0] = "Records start after the final comment line";
String format = "iiifdffffffff";
if (isShowDeviations())
format += "fffffff";
if (isShowEndFrame())
format = "i" + format;
if (isShowId())
format = "i" + format;
comments[1] = "Binary Format (raw Java bytes) = " + format;
return comments;
}
/*
* (non-Javadoc)
*
* @see gdsc.smlm.results.FilePeakResults#getFieldNames()
*/
protected String[] getFieldNames()
{
ArrayList<String> names = new ArrayList<String>(20);
if (isShowId())
names.add("Id");
names.add(peakIdColumnName);
if (isShowEndFrame())
names.add("End " + peakIdColumnName);
names.add("origX");
names.add("origY");
names.add("origValue");
names.add("Error");
names.add("Noise");
names.add("Signal");
String[] fieldNames = new String[] { "Background", "Amplitude", "Angle", "X", "Y", "X SD", "Y SD" };
for (String field : fieldNames)
{
names.add(field);
}
if (isShowDeviations())
{
for (String field : fieldNames)
{
names.add(field + " StdDev");
}
}
return names.toArray(new String[names.size()]);
}
protected void closeOutput()
{
super.closeOutput();
if (out == null)
return;
try
{
out.close();
}
catch (Exception e)
{
// Ignore exception
}
finally
{
out = null;
}
}
/*
* (non-Javadoc)
*
* @see gdsc.smlm.results.FilePeakResults#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;
// Buffer the output for the synchronized write method
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
DataOutputStream buffer = new DataOutputStream(bytes);
try
{
addResult(buffer, 0, peak, peak, origX, origY, origValue, error, noise, params, paramsStdDev);
buffer.flush();
}
catch (IOException e)
{
// Do nothing - This result will not be added to the file
return;
}
writeResult(1, bytes.toByteArray());
}
private void addResult(DataOutputStream buffer, final int id, final int peak, final int endPeak, final int origX,
final int origY, final float origValue, final double error, final float noise, final float[] params,
float[] paramsStdDev) throws IOException
{
if (isShowId())
buffer.writeInt(id);
buffer.writeInt(peak);
if (isShowEndFrame())
buffer.writeInt(endPeak);
buffer.writeInt(origX);
buffer.writeInt(origY);
buffer.writeFloat(origValue);
buffer.writeDouble(error);
buffer.writeFloat(noise);
for (int i = 0; i < 7; i++)
buffer.writeFloat(params[i]);
if (isShowDeviations())
{
if (paramsStdDev == null)
paramsStdDev = new float[7];
for (int i = 0; i < 7; i++)
buffer.writeFloat(paramsStdDev[i]);
}
}
public void addAll(Collection<PeakResult> results)
{
if (out == null)
return;
int count = 0;
// Buffer the output for the synchronized write method
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
DataOutputStream buffer = new DataOutputStream(bytes);
for (PeakResult result : results)
{
try
{
addResult(buffer, result.getId(), result.getFrame(), result.getEndFrame(), result.origX, result.origY,
result.origValue, result.error, result.noise, result.params, result.paramsStdDev);
}
catch (IOException e)
{
// Do nothing - This result will not be added to the file
return;
}
// Flush the output to allow for very large input lists
if (++count >= 20)
{
try
{
buffer.flush();
}
catch (IOException e)
{
// Do nothing - This result will not be added to the file
return;
}
writeResult(count, bytes.toByteArray());
if (!isActive())
return;
bytes.reset();
count = 0;
}
}
try
{
buffer.flush();
}
catch (IOException e)
{
// Do nothing - This result will not be added to the file
return;
}
writeResult(count, bytes.toByteArray());
}
@Override
public void addCluster(Cluster cluster)
{
throw new NoSuchMethodError("Binary results do not support clusters");
}
@Override
public void addComment(String text)
{
throw new NoSuchMethodError("Binary results do not support comments");
}
@Override
public void addTrace(Trace trace)
{
throw new NoSuchMethodError("Binary results do not support traces");
}
private synchronized void writeResult(int count, byte[] bytes)
{
// In case another thread caused the output to close
if (out == null)
return;
size += count;
try
{
out.write(bytes);
}
catch (IOException ioe)
{
closeOutput();
}
}
/*
* (non-Javadoc)
*
* @see gdsc.utils.fitting.PeakResults#size()
*/
public int size()
{
return size;
}
/*
* (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);
DataInputStream input = new DataInputStream(new FileInputStream(filename));
String header;
try
{
header = readHeader(input);
byte[] line = new byte[getDataSize(isShowDeviations(), isShowEndFrame(), isShowId())];
while (input.read(line) == line.length)
{
results.add(new Result(line));
}
}
finally
{
input.close();
}
Collections.sort(results);
// Must write using the same method as the main code so use a FileOutputStream again
FileOutputStream output = new FileOutputStream(filename);
try
{
output.write(header.getBytes());
for (Result result : results)
{
output.write(result.line);
}
}
finally
{
output.close();
}
}
catch (IOException e)
{
// ignore
}
finally
{
out = null;
}
}
/**
* Read all lines from the input stream that begin with '#' and collates them into a header.
* Stops reading if a line contains {@value #END_HEADER}.
* <p>
* Lines are defined by the '\n' character. The input stream will read the first non-header line unless the header
* is terminated by the {@value #END_HEADER} tag.
*
* @param input
* @return The header
* @throws IOException
*/
public static String readHeader(DataInputStream input) throws IOException
{
StringBuffer sb = new StringBuffer();
String line;
do
{
line = readLine(input);
if (line.charAt(0) == '#')
sb.append(line);
else
break;
} while (!line.startsWith("#" + END_HEADER));
return sb.toString();
}
private static String readLine(DataInputStream input) throws IOException
{
StringBuffer sb = new StringBuffer();
byte b;
do
{
b = input.readByte();
sb.append((char) b);
} while (b != '\n');
return sb.toString();
}
public static int getDataSize(boolean deviations, boolean endFrame, boolean id)
{
final int BYTES_INT = 4;
final int BYTES_FLOAT = 4;
final int BYTES_DOUBLE = 8;
// iiifdffffffff
// or
// iiifdfffffffffffffff
// + Extra i added for the end frame after the first integer
// + Extra i added for the id in the first field
int size = 3 * BYTES_INT + BYTES_FLOAT + BYTES_DOUBLE + 8 * BYTES_FLOAT;
if (deviations)
size += 7 * BYTES_FLOAT;
if (endFrame)
size += BYTES_INT;
if (id)
size += BYTES_INT;
return size;
}
private class Result implements Comparable<Result>
{
byte[] line;
int slice = 0;
public Result(byte[] line)
{
this.line = Arrays.copyOf(line, line.length);
extractSlice();
}
private void extractSlice()
{
int offset = (isShowId()) ? 4 : 0;
int ch1 = line[offset + 0] & 0xff;
int ch2 = line[offset + 1] & 0xff;
int ch3 = line[offset + 2] & 0xff;
int ch4 = line[offset + 3] & 0xff;
slice = ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0));
}
public int compareTo(Result o)
{
// Sort by slice number
// (Note: peak height is already done in the run(...) method)
return slice - o.slice;
}
}
/*
* (non-Javadoc)
*
* @see gdsc.utils.fitting.results.PeakResults#isActive()
*/
public boolean isActive()
{
return out != null;
}
/*
* (non-Javadoc)
*
* @see gdsc.smlm.results.FilePeakResults#isBinary()
*/
@Override
public boolean isBinary()
{
return true;
}
}