package gdsc.foci;
/*-----------------------------------------------------------------------------
* GDSC Plugins for ImageJ
*
* Copyright (C) 2011 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 2 of the License, or
* (at your option) any later version.
*---------------------------------------------------------------------------*/
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.LinkedList;
/**
* Manages I/O of the TimeValuePoint class
*/
public class TimeValuePointManager
{
public enum FileType
{
FIND_FOCI, CSV_IdTXYZV, CSV_IdTXY, TAB_IdTXYZV, TAB_IdTXY, QuickPALM, STORMJ, TAB_XYZ, CSV_XYZ, Unknown
}
private static final String newline = System.getProperty("line.separator");
private String filename;
private FileType type = null;
// Used to parse the input file
private int[] fields;
private int maxField;
private String delimiter;
private String line;
private int lineCount;
public TimeValuePointManager(String filename)
{
this.filename = filename;
}
/**
* Attempts to detect the file type by reading the initial lines
*
* @return The type of file containing the points
* @throws IOException
*/
public FileType getFileType() throws IOException
{
if (type != null)
return type;
BufferedReader input = null;
try
{
// Load results from file
input = new BufferedReader(new FileReader(filename));
String line;
int lineCount = 1;
int nonHeaderLines = 0;
while ((line = input.readLine()) != null)
{
type = guessFiletype(line, lineCount++);
if (!line.startsWith("#"))
nonHeaderLines++;
// Stop after several non-header lines
if (type != FileType.Unknown || nonHeaderLines > 1)
break;
}
}
finally
{
try
{
if (input != null)
input.close();
}
catch (IOException e)
{
}
}
return type;
}
private FileType guessFiletype(String line, int lineCount)
{
int delimiterCount = countDelimeters(line, "\t");
// FindFoci file
if (line.startsWith("Peak #\tMask Value") && delimiterCount >= 5)
return FileType.FIND_FOCI;
// Look for a unique text for the QuickPALM file on the first line
if (lineCount == 1 && line.contains("Up-Height") && delimiterCount >= 14)
return FileType.QuickPALM;
// STORMJ file can have extra header lines so ignore line count
if (line.startsWith("#") && line.contains("origX") && delimiterCount >= 12)
return FileType.STORMJ;
// Tab separated fields: ID,T,X,Y,Z,Value
if (delimiterCount >= 5)
return FileType.TAB_IdTXYZV;
// File is allowed to have Z and Value missing
if (delimiterCount >= 3)
return FileType.TAB_IdTXY;
if (delimiterCount == 2)
return FileType.TAB_XYZ;
// Comma separated fields: ID,T,X,Y,Z,Value
delimiterCount = countDelimeters(line, ",");
if (delimiterCount >= 5)
return FileType.CSV_IdTXYZV;
// File is allowed to have Z and Value missing
if (delimiterCount >= 3)
return FileType.CSV_IdTXY;
if (delimiterCount == 2)
return FileType.CSV_XYZ;
return FileType.Unknown;
}
private int countDelimeters(String text, String delimiter)
{
return text.split(delimiter).length - 1;
}
/**
* Save the points to file
*
* @param points
* @throws IOException
*/
public void savePoints(TimeValuedPoint[] points) throws IOException
{
if (points == null)
return;
OutputStreamWriter out = null;
try
{
File file = new File(filename);
if (!file.exists())
{
if (file.getParent() != null)
new File(file.getParent()).mkdirs();
}
// Save results to file
FileOutputStream fos = new FileOutputStream(filename);
out = new OutputStreamWriter(fos);
StringBuilder sb = new StringBuilder();
out.write("ID,T,X,Y,Z,Value" + newline);
// Output all results in ascending rank order
int id = 0;
for (TimeValuedPoint point : points)
{
sb.append(++id).append(',');
sb.append(point.getTime()).append(',');
sb.append(point.getX()).append(',');
sb.append(point.getY()).append(',');
sb.append(point.getZ()).append(',');
sb.append(point.getValue()).append(newline);
out.write(sb.toString());
sb.setLength(0);
}
}
finally
{
try
{
if (out != null)
out.close();
}
catch (IOException e)
{
}
}
}
/**
* Loads the points from the file
*
* @return
* @throws IOException
*/
public TimeValuedPoint[] loadPoints() throws IOException
{
getFileType();
if (type == FileType.Unknown || !setupParser())
return new TimeValuedPoint[0];
LinkedList<TimeValuedPoint> points = new LinkedList<TimeValuedPoint>();
BufferedReader input = null;
try
{
// Load results from file
input = new BufferedReader(new FileReader(filename));
// TODO - Read in binary files from STORMJ
skipHeader(input);
int errors = 0;
while (line != null)
{
String[] tokens = line.split(delimiter);
if (tokens.length > maxField)
{
try
{
//int id = Integer.parseInt(tokens[fields[0]]); // Not currently needed
float t = 1;
if (fields[1] >= 0)
t = Float.parseFloat(tokens[fields[1]]); // QuickPALM uses a float for the time
final float x = Float.parseFloat(tokens[fields[2]]);
final float y = Float.parseFloat(tokens[fields[3]]);
float z = 0;
if (fields[4] >= 0)
{
z = Float.parseFloat(tokens[fields[4]]);
if (type == FileType.QuickPALM)
{
// z is in nm and so must be converted to approximate pixels
float xNm = Float.parseFloat(tokens[4]);
z *= x / xNm;
}
}
float value = 0;
if (fields[5] >= 0)
value = Float.parseFloat(tokens[fields[5]]);
points.add(new TimeValuedPoint(x, y, z, (int) t, value));
}
catch (NumberFormatException e)
{
System.err.println("Invalid numbers on line: " + lineCount);
if (++errors > 10)
break;
}
}
readLine(input);
}
return points.toArray(new TimeValuedPoint[0]);
}
finally
{
try
{
if (input != null)
input.close();
}
catch (IOException e)
{
}
}
}
private boolean setupParser()
{
maxField = 0;
lineCount = 0;
line = null;
switch (type)
{
case FIND_FOCI:
delimiter = "\t";
// Store the object field in T
fields = new int[] { 0, 20, 2, 3, 4, 5 };
break;
case QuickPALM:
delimiter = "\t";
fields = new int[] { 0, 14, 2, 3, 6, 1 };
break;
case STORMJ:
delimiter = "\t";
// No ID or z-dimension
fields = new int[] { -1, 0, 10, 11, -1, 6 };
break;
case TAB_IdTXYZV:
delimiter = "\t";
// ID,T,X,Y,Z,Value
fields = new int[] { 0, 1, 2, 3, 4, 5 };
break;
case TAB_IdTXY:
delimiter = "\t";
// ID,T,X,Y
fields = new int[] { 0, 1, 2, 3, -1, -1 };
break;
case TAB_XYZ:
delimiter = "\t";
// X,Y,Z
fields = new int[] { -1, -1, 0, 1, 2, -1 };
break;
case CSV_IdTXYZV:
delimiter = ",";
// ID,T,X,Y,Z,Value
fields = new int[] { 0, 1, 2, 3, 4, 5 };
break;
case CSV_IdTXY:
delimiter = ",";
// ID,T,X,Y
fields = new int[] { 0, 1, 2, 3, -1, -1 };
break;
case CSV_XYZ:
delimiter = ",";
// X,Y,Z
fields = new int[] { -1, -1, 0, 1, 2, -1 };
break;
default:
return false;
}
for (int i : fields)
if (maxField < i)
maxField = i;
return true;
}
/**
* Reads lines from the input until the first record is reached. Leaves the line variable at the first record.
*
* @param input
* @throws IOException
*/
private void skipHeader(BufferedReader input) throws IOException
{
switch (type)
{
case FIND_FOCI:
do
{
readLine(input);
} while (line != null && (line.startsWith("#") || line.startsWith("Peak #")));
break;
case QuickPALM:
readLine(input); // First line is the header so read this
// Allow fall-through to read the next line
// Read until no comment character
default:
do
{
readLine(input);
} while (line != null && line.startsWith("#"));
break;
}
}
private void readLine(BufferedReader input) throws IOException
{
line = input.readLine();
//if (line != null) // No need as the lineCount is only referenced when line is not null
lineCount++;
}
/**
* Save the points to the given file
*
* @param points
* @param filename
* @throws IOException
*/
public static void savePoints(TimeValuedPoint[] points, String filename) throws IOException
{
TimeValuePointManager manager = new TimeValuePointManager(filename);
manager.savePoints(points);
}
/**
* Loads the points from the file
*
* @param filename
* @return The points
* @throws IOException
*/
public static TimeValuedPoint[] loadPoints(String filename) throws IOException
{
TimeValuePointManager manager = new TimeValuePointManager(filename);
return manager.loadPoints();
}
}