package gdsc.smlm.results; import gdsc.smlm.utils.XmlUtils; /*----------------------------------------------------------------------------- * 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 gdsc.smlm.function.gaussian.Gaussian2DFunction; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.FileOutputStream; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.OutputStreamWriter; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.InputMismatchException; import java.util.NoSuchElementException; import java.util.Scanner; /** * Saves the fit results to file */ public class FilePeakResults extends AbstractPeakResults { // Only write to a single results file protected OutputStreamWriter out = null; protected String filename; private boolean showDeviations = true; private boolean showEndFrame = false; private boolean showId = false; protected boolean sortAfterEnd = false; protected String peakIdColumnName = "Peak"; protected int size = 0; public FilePeakResults(String filename) { this.filename = filename; } public FilePeakResults(String filename, boolean showDeviations) { this.filename = filename; this.showDeviations = showDeviations; } public FilePeakResults(String filename, boolean showDeviations, boolean showEndFrame) { this.filename = filename; this.showDeviations = showDeviations; this.showEndFrame = showEndFrame; } public FilePeakResults(String filename, boolean showDeviations, boolean showEndFrame, boolean showId) { this.filename = filename; this.showDeviations = showDeviations; this.showEndFrame = showEndFrame; this.showId = showId; } /* * (non-Javadoc) * * @see gdsc.utils.fitting.PeakResults#begin() */ public void begin() { out = null; size = 0; try { FileOutputStream fos = new FileOutputStream(filename); out = new OutputStreamWriter(fos, "UTF-8"); out.write(createResultsHeader()); } catch (Exception e) { // TODO - Add better handling of errors e.printStackTrace(); closeOutput(); } } protected String createResultsHeader() { StringBuilder sb = new StringBuilder(); addComment(sb, getHeaderTitle()); sb.append(String.format("#FileVersion %s\n", getVersion())); // Add the standard details if (name != null) sb.append(String.format("#Name %s\n", singleLine(name))); if (source != null) sb.append(String.format("#Source %s\n", singleLine(source.toXML()))); if (bounds != null) sb.append(String.format("#Bounds x%d y%d w%d h%d\n", bounds.x, bounds.y, bounds.width, bounds.height)); if (calibration != null) sb.append(String.format("#Calibration %s\n", singleLine(XmlUtils.toXML(calibration)))); if (configuration != null && configuration.length() > 0) sb.append(String.format("#Configuration %s\n", singleLine(configuration))); // Add any extra comments String[] comments = getHeaderComments(); if (comments != null) { for (String comment : comments) addComment(sb, comment); } // Output the field names String[] fields = getFieldNames(); if (fields != null) { sb.append("#"); for (int i = 0; i < fields.length; i++) { if (i != 0) sb.append("\t"); sb.append(fields[i]); } sb.append('\n'); } addComment(sb, getHeaderEnd()); return sb.toString(); } private void addComment(StringBuilder sb, String comment) { if (comment != null) sb.append("#").append(comment).append('\n'); } /** * @return The first line added to the header */ protected String getHeaderTitle() { return "Localisation Results File"; } /** * @return The last line added to the header (e.g. a header end tag) */ protected String getHeaderEnd() { return null; } /** * @return A line containing the file format version */ protected String getVersion() { StringBuilder sb = new StringBuilder(); sb.append(isBinary() ? "Binary" : "Text"); sb.append("."); sb.append(isShowDeviations() ? "D1" : "D0"); sb.append(".E"); int extended = isShowEndFrame() ? 1 : 0; extended += isShowId() ? 2 : 0; sb.append(extended); // Version 1 had signal and amplitude in the results // Version 2 has only signal in the results sb.append(".V2"); return sb.toString(); } /** * @return Any comment lines to add to the header after the standard output of source, name, bounds, etc. */ protected String[] getHeaderComments() { return null; } /** * @return The names of the fields in each record. Will be the last comment of the header */ protected String[] getFieldNames() { ArrayList<String> names = new ArrayList<String>(20); if (showId) names.add("Id"); names.add(peakIdColumnName); if (showEndFrame) names.add("End " + peakIdColumnName); names.add("origX"); names.add("origY"); names.add("origValue"); names.add("Error"); names.add("Noise"); for (String field : new String[] { "Background", "Signal", "Angle", "X", "Y", "X SD", "Y SD" }) { names.add(field); if (showDeviations) names.add("+/-"); } if (this.calibration != null) names.add("Precision"); return names.toArray(new String[names.size()]); } protected String singleLine(String text) { return text.replaceAll("\n *", ""); } protected void closeOutput() { if (out == null) return; try { out.close(); } catch (Exception e) { // Ignore exception } finally { out = null; } } /* * (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(); addStandardData(sb, 0, peak, peak, origX, origY, origValue, error, noise); // Add the parameters if (showDeviations) { if (paramsStdDev != null) paramsStdDev = new float[7]; addResult(sb, params[0], paramsStdDev[0], params[1], paramsStdDev[1], params[2], paramsStdDev[2], params[3], paramsStdDev[3], params[4], paramsStdDev[4], params[5], paramsStdDev[5], params[6], paramsStdDev[6]); } else { addResult(sb, params[0], params[1], params[2], params[3], params[4], params[5], params[6]); } if (this.calibration != null) { double s = (params[Gaussian2DFunction.X_SD] + params[Gaussian2DFunction.Y_SD]) * 0.5 * calibration.getNmPerPixel(); float precision = (float) PeakResult.getPrecision(calibration.getNmPerPixel(), s, params[Gaussian2DFunction.SIGNAL] / calibration.getGain(), noise / calibration.getGain(), calibration.isEmCCD()); addResult(sb, precision); } sb.append('\n'); writeResult(1, sb.toString()); } private void addStandardData(StringBuilder sb, final int id, final int peak, final int endPeak, final int origX, final int origY, final float origValue, final double chiSquared, final float noise) { if (showId) { sb.append(id); sb.append('\t'); } sb.append(peak); sb.append('\t'); if (showEndFrame) { sb.append(endPeak); sb.append('\t'); } sb.append(origX); sb.append('\t'); sb.append(origY); sb.append('\t'); sb.append(origValue); sb.append('\t'); sb.append(chiSquared); sb.append('\t'); sb.append(noise); } private void addResult(StringBuilder sb, float... args) { for (float f : args) //sb.append(String.format("\t%g", f)); sb.append("\t").append(f); } public void addAll(Collection<PeakResult> results) { if (out == null) return; int count = 0; StringBuilder sb = new StringBuilder(); for (PeakResult result : results) { // Add the standard data addStandardData(sb, result.getId(), result.getFrame(), result.getEndFrame(), result.origX, result.origY, result.origValue, result.error, result.noise); // Add the parameters if (showDeviations) { final float[] paramsStdDev = (result.paramsStdDev != null) ? result.paramsStdDev : new float[7]; addResult(sb, result.params[0], paramsStdDev[0], result.params[1], paramsStdDev[1], result.params[2], paramsStdDev[2], result.params[3], paramsStdDev[3], result.params[4], paramsStdDev[4], result.params[5], paramsStdDev[5], result.params[6], paramsStdDev[6]); } else { addResult(sb, result.params[0], result.params[1], result.params[2], result.params[3], result.params[4], result.params[5], result.params[6]); } if (this.calibration != null) { double s = result.getSD() * calibration.getNmPerPixel(); float precision = (float) PeakResult.getPrecision(calibration.getNmPerPixel(), s, result.params[Gaussian2DFunction.SIGNAL] / calibration.getGain(), result.noise / calibration.getGain(), calibration.isEmCCD()); addResult(sb, precision); } sb.append('\n'); // 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) { if (!showId || cluster.getId() == 0) { addAll(cluster.getPoints()); } else { // Store the ID from the trace final int id = cluster.getId(); ArrayList<PeakResult> results = cluster.getPoints(); ArrayList<PeakResult> results2 = new ArrayList<PeakResult>(results.size()); for (PeakResult result : results) { if (result.getId() == id) results2.add(result); else { results2.add(new ExtendedPeakResult(result.getFrame(), result.origX, result.origY, result.origValue, result.error, result.noise, result.params, result.paramsStdDev, result.getEndFrame(), id)); } } addAll(results2); } } /** * 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); } } /** * Output a comment to the results file. * <p> * Note: This is not synchronised * * @param text */ public void addComment(String text) { if (out == null) return; // Ensure comments are preceded by the comment character if (!text.startsWith("#")) text = "#" + text; if (text.contains("\n")) text.replace("\n", "\n#"); if (!text.endsWith("\n")) text += "\n"; writeResult(0, text); } protected synchronized void writeResult(int count, String result) { // In case another thread caused the output to close if (out == null) return; size += count; try { out.write(result); } 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); 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; } } /** * @param sortAfterEnd * True if the results should be sorted after the {@link #end()} method */ public void setSortAfterEnd(boolean sortAfterEnd) { this.sortAfterEnd = sortAfterEnd; } /** * @return True if the results should be sorted after the {@link #end()} method */ public boolean isSortAfterEnd() { return sortAfterEnd; } 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 { slice = scanner.nextInt(); if (showId) // The peak is the second column 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; } } /** * @return the name of the peak column */ public String getPeakIdColumnName() { return peakIdColumnName; } /** * @param peakIdColumnName * the name of the peak column */ public void setPeakIdColumnName(String peakIdColumnName) { this.peakIdColumnName = peakIdColumnName; } /* * (non-Javadoc) * * @see gdsc.utils.fitting.results.PeakResults#isActive() */ public boolean isActive() { return out != null; } /** * @return True if the records contain the parameter deviations */ public boolean isShowDeviations() { return showDeviations; } /** * @return True if the records contain the result end frame */ public boolean isShowEndFrame() { return showEndFrame; } /** * @return true if the records are stored as binary data */ public boolean isBinary() { return false; } /** * @return True if the records contain a result Id */ public boolean isShowId() { return showId; } }