uk.ac.sussex.gdsc.smlm.results.MalkFilePeakResults Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of gdsc-smlm Show documentation
Show all versions of gdsc-smlm Show documentation
Genome Damage and Stability Centre SMLM Package
Software for single molecule localisation microscopy (SMLM)
The newest version!
/*-
* #%L
* Genome Damage and Stability Centre SMLM Package
*
* Software for single molecule localisation microscopy (SMLM)
* %%
* Copyright (C) 2011 - 2023 Alex Herbert
* %%
* 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.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program. If not, see
* .
* #L%
*/
package uk.ac.sussex.gdsc.smlm.results;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collections;
import java.util.NoSuchElementException;
import java.util.Scanner;
import uk.ac.sussex.gdsc.core.data.utils.ConversionException;
import uk.ac.sussex.gdsc.core.data.utils.IdentityTypeConverter;
import uk.ac.sussex.gdsc.core.data.utils.TypeConverter;
import uk.ac.sussex.gdsc.core.utils.LocalList;
import uk.ac.sussex.gdsc.core.utils.MathUtils;
import uk.ac.sussex.gdsc.smlm.data.config.CalibrationReader;
import uk.ac.sussex.gdsc.smlm.data.config.CalibrationWriter;
import uk.ac.sussex.gdsc.smlm.data.config.PSFProtos.PSFType;
import uk.ac.sussex.gdsc.smlm.data.config.PsfHelper;
import uk.ac.sussex.gdsc.smlm.data.config.UnitProtos.DistanceUnit;
import uk.ac.sussex.gdsc.smlm.data.config.UnitProtos.IntensityUnit;
/**
* 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 {
/**
* Converter to change the distances to nm. It is created in {@link #begin()} but may be null.
*/
protected TypeConverter toNmConverter;
/**
* Converter to change the intensity to photons. It is created in {@link #begin()} but may be
* null.
*/
protected TypeConverter toPhotonConverter;
private Writer out;
/**
* Instantiates a new MALK file peak results.
*
* @param filename the filename
*/
public MalkFilePeakResults(String filename) {
super(filename);
}
@Override
protected String getHeaderEnd() {
return null;
}
@Override
protected String getVersion() {
return "MALK";
}
@Override
protected void openOutput() {
out = new BufferedWriter(new OutputStreamWriter(fos, StandardCharsets.UTF_8));
}
@Override
protected void write(String data) {
try {
out.write(data);
} catch (final IOException ex) {
closeOutput();
}
}
@Override
protected void closeOutput() {
if (fos == null) {
return;
}
try {
// Make sure we close the writer since it may be buffered
out.close();
} catch (final Exception ignored) {
// Ignore exception
} finally {
fos = null;
}
}
@Override
public synchronized void begin() {
// Ensure we write out in nm and photons if possible.
if (hasCalibration()) {
// Copy it so it can be modified
final CalibrationWriter cw = new CalibrationWriter(getCalibration());
// Create converters
try {
toNmConverter = cw.getDistanceConverter(DistanceUnit.NM);
cw.setDistanceUnit(DistanceUnit.NM);
} catch (final ConversionException ignored) {
// Gracefully fail so ignore this
}
try {
toPhotonConverter = cw.getIntensityConverter(IntensityUnit.PHOTON);
cw.setIntensityUnit(IntensityUnit.PHOTON);
} catch (final ConversionException ignored) {
// Gracefully fail so ignore this
}
setCalibration(cw.getCalibration());
}
// The data loses PSF information so reset this to a custom type with
// no additional parameters.
setPsf(PsfHelper.create(PSFType.CUSTOM));
super.begin();
// Create converters to avoid null pointers
if (toNmConverter == null) {
toNmConverter = new IdentityTypeConverter<>(null);
}
if (toPhotonConverter == null) {
toPhotonConverter = new IdentityTypeConverter<>(null);
}
}
@Override
protected String[] getHeaderComments() {
final String[] comments = new String[3];
int count = 0;
if (hasCalibration()) {
final CalibrationReader cr = getCalibrationReader();
if (cr.hasNmPerPixel()) {
comments[count++] =
String.format("Pixel pitch %s (nm)", MathUtils.rounded(cr.getNmPerPixel()));
}
if (cr.hasCountPerPhoton()) {
comments[count++] =
String.format("Gain %s (Count/photon)", MathUtils.rounded(cr.getCountPerPhoton()));
}
if (cr.hasExposureTime()) {
comments[count++] = String.format("Exposure time %s (seconds)",
MathUtils.rounded(cr.getExposureTime() * 1e-3));
}
}
return Arrays.copyOf(comments, count);
}
@Override
protected String[] getFieldNames() {
final String[] names = {"X", "Y", "Frame", "Signal"};
if (toNmConverter != null) {
names[0] += " (nm)";
names[1] += " (nm)";
}
if (toPhotonConverter != null) {
names[3] += " (photon)";
}
return names;
}
@Override
public void add(int peak, int origX, int origY, float origValue, double error, float noise,
float meanIntensity, float[] params, float[] paramsStdDev) {
if (fos == null) {
return;
}
final StringBuilder sb = new StringBuilder(100);
addStandardData(sb, params[PeakResult.X], params[PeakResult.Y], peak,
params[PeakResult.INTENSITY]);
writeResult(1, sb.toString());
}
@Override
public void add(PeakResult result) {
if (fos == null) {
return;
}
final StringBuilder sb = new StringBuilder(100);
addStandardData(sb, result.getXPosition(), result.getYPosition(), result.getFrame(),
result.getIntensity());
writeResult(1, sb.toString());
}
private void addStandardData(StringBuilder sb, final float x, final float y, final int frame,
final float signal) {
// @formatter:off
sb.append(toNmConverter.convert(x)).append('\t')
.append(toNmConverter.convert(y)).append('\t')
.append(frame).append('\t')
.append(toPhotonConverter.convert(signal)).append('\n');
// @formatter:on
}
@Override
public void addAll(PeakResult[] results) {
if (fos == null) {
return;
}
int count = 0;
final StringBuilder sb = new StringBuilder(2000);
for (final PeakResult result : results) {
// Add the standard data
addStandardData(sb, result.getXPosition(), result.getYPosition(), result.getFrame(),
result.getIntensity());
// 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());
}
/**
* Adds all the results in the cluster.
*
* @param cluster the cluster
*/
protected void addAll(Cluster cluster) {
addAll(cluster.getPoints());
}
@Override
protected void sort() throws IOException {
final LocalList results = new LocalList<>(size);
final StringBuilder header = new StringBuilder();
final Path path = Paths.get(filename);
try (BufferedReader input = Files.newBufferedReader(path)) {
String line;
// Skip the header
for (line = input.readLine(); line != null; line = input.readLine()) {
if (line.charAt(0) != '#') {
// This is the first record
results.add(new Result(line));
break;
}
header.append(line).append(System.lineSeparator());
}
for (line = input.readLine(); line != null; line = input.readLine()) {
results.add(new Result(line));
}
}
Collections.sort(results, (r1, r2) -> Integer.compare(r1.slice, r2.slice));
try (BufferedWriter output = Files.newBufferedWriter(path)) {
output.write(header.toString());
for (int i = 0; i < results.size(); i++) {
output.write(results.unsafeGet(i).line);
output.newLine();
}
}
}
/**
* Store the String representation of the result with the frame (slice) number to be used for
* sorting.
*/
private static class Result {
String line;
int slice;
Result(String line) {
this.line = line;
extractSlice();
}
private void extractSlice() {
try (Scanner scanner = new Scanner(line)) {
scanner.useDelimiter("\t");
scanner.nextFloat(); // X
scanner.nextFloat(); // Y
slice = scanner.nextInt();
} catch (final NoSuchElementException ignored) {
// Ignore
}
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy