package gdsc.smlm.ij.plugins; /*----------------------------------------------------------------------------- * 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 gdsc.smlm.ij.IJImageSource; import gdsc.core.ij.IJTrackProgress; import gdsc.smlm.ij.results.IJImagePeakResults; import gdsc.smlm.ij.results.IJTablePeakResults; import gdsc.smlm.ij.results.ImagePeakResultsFactory; import gdsc.smlm.ij.results.ResultsFileFormat; import gdsc.smlm.ij.results.ResultsImage; import gdsc.smlm.ij.results.ResultsMode; import gdsc.smlm.ij.results.ResultsTable; import gdsc.smlm.ij.settings.Constants; import gdsc.smlm.ij.settings.GlobalSettings; import gdsc.smlm.ij.settings.ResultsSettings; import gdsc.smlm.ij.settings.SettingsManager; import gdsc.core.ij.Utils; import gdsc.core.utils.TurboList; import gdsc.smlm.results.BinaryFilePeakResults; import gdsc.smlm.results.Calibration; import gdsc.smlm.results.ExtendedPeakResult; import gdsc.smlm.results.FileFormat; import gdsc.smlm.results.FilePeakResults; import gdsc.smlm.results.MALKFilePeakResults; import gdsc.smlm.results.MemoryPeakResults; import gdsc.smlm.results.PeakResult; import gdsc.smlm.results.PeakResults; import gdsc.smlm.results.PeakResultsList; import gdsc.smlm.results.PeakResultsReader; import gdsc.smlm.results.ResultOption; import gdsc.smlm.results.TSFPeakResultsWriter; import ij.IJ; import ij.ImagePlus; import ij.Prefs; import ij.WindowManager; import ij.gui.GenericDialog; import ij.gui.YesNoCancelDialog; import ij.io.OpenDialog; import ij.plugin.PlugIn; import ij.plugin.frame.Recorder; import java.awt.Rectangle; import java.awt.TextField; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.geom.Rectangle2D; import java.io.File; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedList; import java.util.List; /** * Opens peaks results and displays/converts them */ public class ResultsManager implements PlugIn, MouseListener { public enum InputSource { //@formatter:off FILE{ public String getName() { return "File"; }}, MEMORY{ public String getName() { return "Memory"; }}, MEMORY_MULTI_FRAME{ public String getName() { return "Memory (Multi-Frame)"; }}, MEMORY_SINGLE_FRAME{ public String getName() { return "Memory (Single-Frame)"; }}, MEMORY_CLUSTERED{ public String getName() { return "Memory (Clustered)"; }}, NONE{ public String getName() { return "None"; }}; //@formatter:on @Override public String toString() { return getName(); } /** * Gets the name. * * @return the name */ abstract public String getName(); } private static String TITLE = "Peak Results Manager"; static String INPUT_FILE = "File"; static String INPUT_MEMORY = "Memory"; static String INPUT_NONE = "[None]"; private static String inputOption = ""; private static String inputFilename = Prefs.get(Constants.inputFilename, ""); private static boolean chooseRoi = false; private static String roiImage = ""; private Rectangle roiBounds; private int roiImageWidth, roiImageHeight; private ResultsSettings resultsSettings = new ResultsSettings(); private boolean fileInput = false; private GenericDialog gd; private TextField text1; private TextField text2; private static double input_nmPerPixel = Prefs.get(Constants.inputNmPerPixel, 0); private static double input_gain = Prefs.get(Constants.inputGain, 1); private static double input_exposureTime = Prefs.get(Constants.inputExposureTime, 0); private static float input_noise = (float) Prefs.get(Constants.inputNoise, 0); private static ArrayList<String> selected; /* * (non-Javadoc) * * @see ij.plugin.PlugIn#run(java.lang.String) */ public void run(String arg) { SMLMUsageTracker.recordPlugin(this.getClass(), arg); if (arg != null && arg.startsWith("clear")) { Collection<MemoryPeakResults> allResults; boolean removeAll = false; if (arg.contains("multi")) { MultiDialog md = new MultiDialog(TITLE, new MultiDialog.MemoryResultsItems()); md.addSelected(selected); md.showDialog(); if (md.wasCanceled()) return; selected = md.getSelectedResults(); if (selected.isEmpty()) return; allResults = new ArrayList<MemoryPeakResults>(selected.size()); for (String name : selected) { MemoryPeakResults r = MemoryPeakResults.getResults(name); if (r != null) allResults.add(r); } } else { removeAll = true; allResults = MemoryPeakResults.getAllResults(); } if (allResults.isEmpty()) return; long memorySize = 0; int size = 0; for (MemoryPeakResults results : allResults) { memorySize += MemoryPeakResults.estimateMemorySize(results.getResults()); size += results.size(); } String memory = MemoryPeakResults.memorySizeString(memorySize); String count = Utils.pleural(size, "result"); String sets = Utils.pleural(allResults.size(), "set"); GenericDialog gd = new GenericDialog(TITLE); gd.addMessage(String.format("Do you want to remove %s from memory (%s, %s)?", count, sets, memory)); gd.enableYesNoCancel(); gd.showDialog(); if (!gd.wasOKed()) return; if (removeAll) MemoryPeakResults.clearMemory(); else { for (MemoryPeakResults results : allResults) MemoryPeakResults.removeResults(results.getName()); } SummariseResults.clearSummaryTable(); IJ.log(String.format("Cleared %s (%s, %s)", count, sets, memory)); return; } if (!showDialog()) return; MemoryPeakResults results = loadResults(inputOption); if (results == null || results.size() == 0) { IJ.error(TITLE, "No results could be loaded"); IJ.showStatus(""); return; } results = cropToRoi(results); if (results.size() == 0) { IJ.error(TITLE, "No results within the crop region"); return; } if (resultsSettings.resultsInMemory && fileInput) MemoryPeakResults.addResults(results); IJ.showStatus("Processing outputs ..."); Rectangle bounds = results.getBounds(true); boolean showDeviations = resultsSettings.showDeviations && canShowDeviations(results); boolean showEndFrame = canShowEndFrame(results); boolean showId = canShowId(results); // Display the configured output PeakResultsList output = new PeakResultsList(); output.copySettings(results); //String title = results.getSource(); //if (title == null || title.length() == 0) // output.setSource(TITLE); addTableResults(results, output, showDeviations, showEndFrame); addImageResults(output, results.getName(), bounds, results.getNmPerPixel(), results.getGain()); addFileResults(output, showDeviations, showEndFrame, showId); output.begin(); // Process in batches to provide progress List<PeakResult> list = results.getResults(); int progress = 0; int totalProgress = list.size(); int stepProgress = Utils.getProgressInterval(totalProgress); TurboList<PeakResult> batch = new TurboList<PeakResult>(stepProgress); for (PeakResult result : list) { if (progress % stepProgress == 0) { IJ.showProgress(progress, totalProgress); } progress++; batch.addf(result); if (batch.size() == stepProgress) { output.addAll(batch); batch.clearf(); if (isInterrupted()) break; } } IJ.showProgress(1); output.end(); IJ.showStatus(String.format("Processed %d result%s", results.size(), (results.size() > 1) ? "s" : "")); } /** * Check if the escape key has been pressed. Show a status aborted message if true. * * @return True if aborted */ public static boolean isInterrupted() { if (IJ.escapePressed()) { IJ.beep(); IJ.showStatus("Aborted"); return true; } return false; } private boolean canShowDeviations(MemoryPeakResults results) { for (PeakResult r : results) if (r.paramsStdDev != null) return true; return false; } private boolean canShowEndFrame(MemoryPeakResults results) { for (PeakResult r : results.getResults()) if (r.getFrame() != r.getEndFrame()) return true; return false; } private boolean canShowId(MemoryPeakResults results) { final int id = results.getHead().getId(); for (PeakResult r : results.getResults()) if (id != r.getId()) return true; return false; } private void addTableResults(MemoryPeakResults results, PeakResultsList resultsList, boolean showDeviations, boolean showEndFrame) { if (resultsSettings.getResultsTable() != ResultsTable.NONE) { IJTablePeakResults r = new IJTablePeakResults(showDeviations); r.setPeakIdColumnName("Frame"); r.setShowCalibratedValues(resultsSettings.getResultsTable() == ResultsTable.CALIBRATED); // Get a bias if required Calibration calibration = results.getCalibration(); if (r.isShowCalibratedValues() && calibration.getBias() == 0) { GenericDialog gd = new GenericDialog(TITLE); gd.addMessage("Calibrated results requires a camera bias"); gd.addNumericField("Camera_bias (ADUs)", calibration.getBias(), 2); gd.showDialog(); if (!gd.wasCanceled()) { calibration.setBias(Math.abs(gd.getNextNumber())); } } r.setShowEndFrame(showEndFrame); resultsList.addOutput(r); } } private void addImageResults(PeakResultsList resultsList, String title, Rectangle bounds, double nmPerPixel, double gain) { if (resultsSettings.getResultsImage() != ResultsImage.NONE) { IJImagePeakResults image = ImagePeakResultsFactory.createPeakResultsImage(resultsSettings.getResultsImage(), resultsSettings.weightedImage, resultsSettings.equalisedImage, title, bounds, nmPerPixel, gain, resultsSettings.imageScale, resultsSettings.precision, ResultsMode.ADD); image.setRollingWindowSize(resultsSettings.imageRollingWindow); image.setRepaintDelay(2000); resultsList.addOutput(image); } } private void addFileResults(PeakResultsList resultsList, boolean showDeviations, boolean showEndFrame, boolean showId) { if (resultsSettings.resultsFilename != null && resultsSettings.resultsFilename.length() > 0) { // Remove extension resultsSettings.resultsFilename = Utils.replaceExtension(resultsSettings.resultsFilename, resultsSettings.getResultsFileFormat().getExtension()); if (fileInput && inputFilename.equals(resultsSettings.resultsFilename)) { IJ.log(TITLE + ": Input and output files are the same, skipping output ..."); return; } // Check if file exists File file = new File(resultsSettings.resultsFilename); if (file.exists()) { YesNoCancelDialog d = new YesNoCancelDialog(IJ.getInstance(), TITLE, "Overwrite existing file?\n" + resultsSettings.resultsFilename); if (!d.yesPressed()) return; } File parent = file.getParentFile(); if (parent != null && parent.exists()) { PeakResults r; switch (resultsSettings.getResultsFileFormat()) { case GDSC_BINARY: r = new BinaryFilePeakResults(resultsSettings.resultsFilename, showDeviations, showEndFrame, showId); break; case GDSC_TEXT: r = new FilePeakResults(resultsSettings.resultsFilename, showDeviations, showEndFrame, showId); break; case MALK: r = new MALKFilePeakResults(resultsSettings.resultsFilename); break; case TSF: r = new TSFPeakResultsWriter(resultsSettings.resultsFilename); break; default: throw new RuntimeException( "Unsupported file format: " + resultsSettings.getResultsFileFormat()); } if (r instanceof FilePeakResults) { FilePeakResults fr = (FilePeakResults) r; fr.setPeakIdColumnName("Frame"); } resultsList.addOutput(r); } } } private boolean showDialog() { gd = new GenericDialog(TITLE); gd.addHelp(About.HELP_URL); // Build a list of all images with a region ROI List<String> titles = new LinkedList<String>(); if (WindowManager.getWindowCount() > 0) { for (int imageID : WindowManager.getIDList()) { ImagePlus imp = WindowManager.getImage(imageID); if (imp != null && imp.getRoi() != null && imp.getRoi().isArea()) titles.add(imp.getTitle()); } } GlobalSettings settings = SettingsManager.loadSettings(); resultsSettings = settings.getResultsSettings(); gd.addMessage("Read the Peak Results and output to a new format"); gd.addMessage("Select the Peak Results"); addInput(gd, inputOption, InputSource.MEMORY, InputSource.FILE); if (!titles.isEmpty()) gd.addCheckbox((titles.size() == 1) ? "Use_ROI" : "Choose_ROI", chooseRoi); gd.addMessage("--- Table output ---"); String[] tableNames = SettingsManager.getNames((Object[]) ResultsTable.values()); gd.addChoice("Results_table", tableNames, tableNames[resultsSettings.getResultsTable().ordinal()]); gd.addCheckbox("Show_deviations", resultsSettings.showDeviations); gd.addMessage("--- Image output ---"); String[] imageNames = SettingsManager.getNames((Object[]) ResultsImage.values()); gd.addChoice("Image", imageNames, imageNames[resultsSettings.getResultsImage().ordinal()]); gd.addCheckbox("Weighted", resultsSettings.weightedImage); gd.addCheckbox("Equalised", resultsSettings.equalisedImage); gd.addSlider("Image_Precision (nm)", 5, 30, resultsSettings.precision); gd.addSlider("Image_Scale", 1, 15, resultsSettings.imageScale); gd.addNumericField("Image_Window", resultsSettings.imageRollingWindow, 0); gd.addMessage("--- File output ---"); // Do not add a results file to prevent constant overwrite messages gd.addStringField("Results_file", ""); String[] formatNames = SettingsManager.getNames((Object[]) ResultsFileFormat.values()); gd.addChoice("Results_format", formatNames, formatNames[resultsSettings.getResultsFileFormat().ordinal()]); gd.addMessage(" "); gd.addCheckbox("Results_in_memory (file input only)", resultsSettings.resultsInMemory); // Dialog to allow double click to select files using a file chooser if (Utils.isShowGenericDialog()) { text1 = (TextField) gd.getStringFields().get(0); // Input file text2 = (TextField) gd.getStringFields().get(1); // Results file text1.addMouseListener(this); text2.addMouseListener(this); text1.setColumns(30); text2.setColumns(30); } gd.showDialog(); if (gd.wasCanceled()) return false; inputOption = ResultsManager.getInputSource(gd); inputFilename = gd.getNextString(); if (!titles.isEmpty()) chooseRoi = gd.getNextBoolean(); resultsSettings.setResultsTable(gd.getNextChoiceIndex()); resultsSettings.showDeviations = gd.getNextBoolean(); resultsSettings.setResultsImage(gd.getNextChoiceIndex()); resultsSettings.weightedImage = gd.getNextBoolean(); resultsSettings.equalisedImage = gd.getNextBoolean(); resultsSettings.precision = gd.getNextNumber(); resultsSettings.imageScale = gd.getNextNumber(); resultsSettings.imageRollingWindow = (int) gd.getNextNumber(); resultsSettings.resultsFilename = gd.getNextString(); resultsSettings.setResultsFileFormat(gd.getNextChoiceIndex()); resultsSettings.resultsInMemory = gd.getNextBoolean(); // Check arguments try { if (resultsSettings.getResultsImage() == ResultsImage.SIGNAL_AV_PRECISION || resultsSettings.getResultsImage() == ResultsImage.LOCALISATIONS_AV_PRECISION) { Parameters.isAboveZero("Image precision", resultsSettings.precision); } Parameters.isAboveZero("Image scale", resultsSettings.imageScale); Parameters.isPositive("Image rolling window", resultsSettings.imageRollingWindow); } catch (IllegalArgumentException e) { IJ.error(TITLE, e.getMessage()); return false; } Prefs.set(Constants.inputFilename, inputFilename); if (!titles.isEmpty() && chooseRoi) { if (titles.size() == 1) { roiImage = titles.get(0); Recorder.recordOption("Image", roiImage); } else { String[] items = titles.toArray(new String[titles.size()]); gd = new GenericDialog(TITLE); gd.addMessage("Select the source image for the ROI"); gd.addChoice("Image", items, roiImage); gd.showDialog(); if (gd.wasCanceled()) return false; roiImage = gd.getNextChoice(); } ImagePlus imp = WindowManager.getImage(roiImage); roiBounds = imp.getRoi().getBounds(); roiImageWidth = imp.getWidth(); roiImageHeight = imp.getHeight(); } else { roiBounds = null; } SettingsManager.saveSettings(settings); return true; } /** * Add a list of input sources to the generic dialog. The choice field will be named 'input'. If a file input option * is selected then a field will be added name 'Input_file'. * <p> * If the source is a memory source then it will not be added if it is empty. If not empty then a summary of the * number of localisation is added as a message to the dialog. * * @param gd * @param inputOption * @param inputs */ public static void addInput(GenericDialog gd, String inputOption, InputSource... inputs) { addInput(gd, "Input", inputOption, inputs); } /** * Add a list of input sources to the generic dialog. The choice field will be named inputName. If a file input * option * is selected then a field will be added name 'Input_file'. * <p> * If the source is a memory source then it will not be added if it is empty. If not empty then a summary of the * number of localisation is added as a message to the dialog. * * @param gd * @param inputName * @param inputOption * @param inputs */ public static void addInput(GenericDialog gd, String inputName, String inputOption, InputSource... inputs) { ArrayList<String> source = new ArrayList<String>(3); boolean fileInput = false; for (InputSource input : inputs) { ResultsManager.addInputSource(source, input); if (input == InputSource.FILE) fileInput = true; } if (source.isEmpty()) addInputSource(source, InputSource.NONE); addInputSourceToDialog(gd, inputName, inputOption, source, fileInput); } /** * Add a list of input sources to the generic dialog. The choice field will be named inputName. If the file input * option * is true then a field will be added name 'Input_file'. * * @param gd * @param inputName * @param inputOption * The option to select by default * @param source * @param fileInput */ public static void addInputSourceToDialog(GenericDialog gd, String inputName, String inputOption, ArrayList<String> source, boolean fileInput) { String[] options = source.toArray(new String[source.size()]); // Find the option inputOption = removeFormatting(inputOption); int optionIndex = 0; for (int i = 0; i < options.length; i++) { String name = removeFormatting(options[i]); if (name.equals(inputOption)) { optionIndex = i; break; } } gd.addChoice(inputName, options, options[optionIndex]); if (fileInput) gd.addStringField("Input_file", inputFilename, 30); } /** * Remove the extra information added to a name for use in dialogs * * @param name * the formatted name * @return The name */ public static String removeFormatting(String name) { int index = name.lastIndexOf('['); if (index > 0) name = name.substring(0, index - 1); return name; } /** * Add an input source the list. If the source is a memory source then it will not be added if it is * empty. If not empty then a summary of the number of localisation is added as a message to the dialog. * * @param source * @param input */ public static void addInputSource(ArrayList<String> source, InputSource input) { switch (input) { case NONE: source.add(INPUT_NONE); break; case FILE: source.add(INPUT_FILE); break; case MEMORY: case MEMORY_MULTI_FRAME: case MEMORY_SINGLE_FRAME: case MEMORY_CLUSTERED: for (String name : MemoryPeakResults.getResultNames()) { addInputSource(source, MemoryPeakResults.getResults(name), input); } break; } } /** * Add a memory input source to the list * * @param source * @param memoryResults * @param input * MEMORY_MULTI_FRAME : Select only those results with at least one result spanning frames, * MEMORY_CLUSTERED : Select only those results which have at least some IDs above zero (allowing zero to * be a valid cluster Id for no cluster) */ public static void addInputSource(ArrayList<String> source, MemoryPeakResults memoryResults, InputSource input) { if (memoryResults.size() > 0) { switch (input) { case MEMORY_MULTI_FRAME: if (!isMultiFrame(memoryResults)) return; break; case MEMORY_SINGLE_FRAME: if (isMultiFrame(memoryResults)) return; break; case MEMORY_CLUSTERED: if (!hasID(memoryResults)) return; break; default: } source.add(getName(memoryResults)); } } /** * Get the name of the results for use in dialogs * * @param memoryResults * @return The name */ public static String getName(MemoryPeakResults memoryResults) { return memoryResults.getName() + " [" + memoryResults.size() + "]"; } /** * Load results from memory using a name from a dialog * * @param name * @return The results */ public static MemoryPeakResults loadMemoryResults(String name) { return MemoryPeakResults.getResults(removeFormatting(name)); } /** * Check for multi-frame results. * * @param memoryResults * @return True if at least one result spanning frames */ public static boolean isMultiFrame(MemoryPeakResults memoryResults) { for (PeakResult r : memoryResults.getResults()) if (r.getFrame() < r.getEndFrame()) return true; return false; } /** * Check for any IDs above zero. * * @param memoryResults * @return True if any results have IDs above zero */ public static boolean hasID(MemoryPeakResults memoryResults) { for (PeakResult r : memoryResults.getResults()) if (r.getId() > 0) return true; return false; } /** * Check for all IDs above zero. * * @param memoryResults * @return True if all results have IDs above zero */ public static boolean isID(MemoryPeakResults memoryResults) { for (PeakResult r : memoryResults.getResults()) if (r.getId() <= 0) return false; return true; } /** * All results must be an ExtendedPeakResult. * * @param memoryResults * @return True if all ExtendedPeakResult */ public static boolean isExtended(MemoryPeakResults memoryResults) { for (PeakResult r : memoryResults.getResults()) if (!(r instanceof ExtendedPeakResult)) return false; return true; } /** * Gets the name of the next input source from the dialog * * @param gd */ public static String getInputSource(GenericDialog gd) { String source = gd.getNextChoice(); return removeFormatting(source); } /** * Load the results from the named input option * * @param inputOption * @param checkCalibration * Set to true to ensure the results have a valid calibration * @return */ public static MemoryPeakResults loadInputResults(String inputOption, boolean checkCalibration) { MemoryPeakResults results = null; PeakResultsReader reader = null; if (inputOption.equals(INPUT_NONE)) { } else if (inputOption.equals(INPUT_FILE)) { IJ.showStatus("Reading results file ..."); reader = new PeakResultsReader(inputFilename); IJ.showStatus("Reading " + reader.getFormat() + " results file ..."); ResultOption[] options = reader.getOptions(); if (options != null) collectOptions(reader, options); reader.setTracker(new IJTrackProgress()); results = reader.getResults(); reader.getTracker().progress(1.0); if (results != null && results.size() > 0) { // If the name contains a .tif suffix then create an image source if (results.getName() != null && results.getName().contains(".tif") && results.getSource() == null) { int index = results.getName().indexOf(".tif"); results.setSource(new IJImageSource(results.getName().substring(0, index))); } } } else { results = loadMemoryResults(inputOption); } if (results != null && results.size() > 0 && checkCalibration) { if (!checkCalibration(results, reader)) results = null; } IJ.showStatus(""); return results; } private static void collectOptions(PeakResultsReader reader, ResultOption[] options) { GenericDialog gd = new GenericDialog(TITLE); gd.addMessage("Options required for file format: " + reader.getFormat().getName()); for (ResultOption option : options) { if (option.hasValues()) { String[] items = new String[option.values.length]; for (int i = 0; i < items.length; i++) items[i] = option.values[i].toString(); gd.addChoice(getName(option), items, option.getValue().toString()); } else if (option.getValue() instanceof Number) { Number n = (Number) option.getValue(); if (n.doubleValue() == n.intValue()) { gd.addNumericField(getName(option), n.intValue(), 0); } else { String value = n.toString(); int sig = 0; int index = value.indexOf('.'); if (index != -1) { // There is a decimal point. Count the digits after it while (++index < value.length()) { if (!Character.isDigit(value.charAt(index))) { // A non-digit character after the decimal point is for scientific notation sig = -sig; break; } sig++; } } gd.addNumericField(getName(option), n.doubleValue(), sig); } } else if (option.getValue() instanceof String) { gd.addStringField(getName(option), (String) option.getValue()); } else if (option.getValue() instanceof Boolean) { gd.addCheckbox(getName(option), (Boolean) option.getValue()); } else { IJ.log(TITLE + ": Unsupported reader option: " + option.name + "=" + option.getValue().toString()); } } gd.showDialog(); if (gd.wasCanceled()) return; try { for (ResultOption option : options) { if (option.hasValues()) { option.setValue(option.values[gd.getNextChoiceIndex()]); } else if (option.getValue() instanceof Number) { double d = gd.getNextNumber(); // Convert to the correct type using the String value constructor for the number option.setValue( option.getValue().getClass().getConstructor(String.class).newInstance(Double.toString(d))); } else if (option.getValue() instanceof String) { option.setValue(gd.getNextString()); } else if (option.getValue() instanceof Boolean) { option.setValue(gd.getNextBoolean()); } } reader.setOptions(options); } catch (Exception e) { // This can occur if the options are not valid IJ.log(TITLE + ": Failed to configure reader options: " + e.getMessage()); } } private static String getName(ResultOption option) { return option.name.replace(' ', '_'); } /** * Check the calibration of the results exists, if not then prompt for it with a dialog * * @param results * The results * @return True if OK; false if calibration dialog cancelled */ public static boolean checkCalibration(MemoryPeakResults results) { return checkCalibration(results, null); } /** * Check the calibration of the results exists, if not then prompt for it with a dialog * * @param results * The results * @param reader * Used to determine the file type * @return True if OK; false if calibration dialog cancelled */ private static boolean checkCalibration(MemoryPeakResults results, PeakResultsReader reader) { // Check for Calibration Calibration calibration = results.getCalibration(); String msg = "partially calibrated"; if (calibration == null) { // Make sure the user knows all the values have not been set calibration = new Calibration(); msg = "uncalibrated"; } else { // Validate to set the valid flags calibration.validate(); } final float noise = getNoise(results); // Only check for essential calibration settings (i.e. not readNoise, bias, emCCD, amplification) if (!calibration.hasNmPerPixel() || !calibration.hasGain() || !calibration.hasExposureTime() || noise <= 0) { boolean convert = false; // We may have results that are within configured bounds. If so we do not need the conversion boolean showConvert = true; if (results.getBounds(false) != null) { final Rectangle bounds = results.getBounds(false); showConvert = false; for (PeakResult r : results.getResults()) { if (!bounds.contains(r.getXPosition(), r.getYPosition())) { showConvert = true; break; } } } if (!calibration.hasNmPerPixel()) calibration.setNmPerPixel(input_nmPerPixel); if (!calibration.hasGain()) calibration.setGain(input_gain); if (!calibration.hasExposureTime()) calibration.setExposureTime(input_exposureTime); Rectangle2D.Float dataBounds = results.getDataBounds(); GenericDialog gd = new GenericDialog(TITLE); gd.addMessage( String.format("Results are %s.\nData bounds = (%s,%s) to (%s,%s)", msg, Utils.rounded(dataBounds.x), Utils.rounded(dataBounds.y), Utils.rounded(dataBounds.y + dataBounds.getWidth()), Utils.rounded(dataBounds.x + dataBounds.getHeight()))); gd.addNumericField("Calibration (nm/px)", calibration.getNmPerPixel(), 2); gd.addNumericField("Gain (ADU/photon)", calibration.getGain(), 2); gd.addNumericField("Exposure_time (ms)", calibration.getExposureTime(), 2); if (noise <= 0) gd.addNumericField("Noise (ADU)", input_noise, 2); if (showConvert) gd.addCheckbox("Convert_nm_to_pixels", convert); gd.showDialog(); if (gd.wasCanceled()) return false; input_nmPerPixel = Math.abs(gd.getNextNumber()); input_gain = Math.abs(gd.getNextNumber()); input_exposureTime = Math.abs(gd.getNextNumber()); if (noise == 0) input_noise = Math.abs((float) gd.getNextNumber()); if (showConvert) convert = gd.getNextBoolean(); Prefs.set(Constants.inputNmPerPixel, input_nmPerPixel); Prefs.set(Constants.inputGain, input_gain); Prefs.set(Constants.inputExposureTime, input_exposureTime); Prefs.set(Constants.inputNoise, input_noise); results.setCalibration(new Calibration(input_nmPerPixel, input_gain, input_exposureTime)); if (convert && input_nmPerPixel > 0) { // Note: NSTORM stores 2xSD final double widthConversion = (reader != null && reader.getFormat() == FileFormat.NSTORM) ? 1.0 / (2 * input_nmPerPixel) : 1.0 / input_nmPerPixel; for (PeakResult p : results.getResults()) { p.params[Gaussian2DFunction.X_POSITION] /= input_nmPerPixel; p.params[Gaussian2DFunction.Y_POSITION] /= input_nmPerPixel; p.params[Gaussian2DFunction.X_SD] *= widthConversion; p.params[Gaussian2DFunction.Y_SD] *= widthConversion; } } if (noise == 0) { for (PeakResult p : results.getResults()) { p.noise = input_noise; } } } return true; } /** * get the first non-zero noise value * * @param results * @return The noise (zero if no results have a noise value) */ private static float getNoise(MemoryPeakResults results) { for (PeakResult r : results.getResults()) { if (r.noise != 0) return r.noise; } return 0; } /** * Load the results from the named input option * * @param inputOption * @return */ private MemoryPeakResults loadResults(String inputOption) { if (inputOption.equals(INPUT_FILE)) { fileInput = true; } return loadInputResults(inputOption, true); } public void mouseClicked(MouseEvent e) { if (e.getClickCount() > 1) // Double-click { TextField text = (e.getSource() == text1) ? text1 : text2; String filename = text.getText(); // We initialised the result filename input box to empty. // We can use the last filename if the input box is still empty. if (Utils.isNullOrEmpty(filename) && text == text2) { filename = resultsSettings.resultsFilename; } String[] path = Utils.decodePath(filename); OpenDialog chooser = new OpenDialog("Coordinate file", path[0], path[1]); if (chooser.getFileName() != null) { text.setText(chooser.getDirectory() + chooser.getFileName()); } } } public void mousePressed(MouseEvent e) { } public void mouseReleased(MouseEvent e) { } public void mouseEntered(MouseEvent e) { } public void mouseExited(MouseEvent e) { } private MemoryPeakResults cropToRoi(MemoryPeakResults results) { if (roiBounds == null) return results; // Adjust bounds relative to input results image double xscale = roiImageWidth / results.getBounds().width; double yscale = roiImageHeight / results.getBounds().height; roiBounds.x /= xscale; roiBounds.width /= xscale; roiBounds.y /= yscale; roiBounds.height /= yscale; float minX = (int) (roiBounds.x); float maxX = (int) Math.ceil(roiBounds.x + roiBounds.width); float minY = (int) (roiBounds.y); float maxY = (int) Math.ceil(roiBounds.y + roiBounds.height); // Create a new set of results within the bounds MemoryPeakResults newResults = new MemoryPeakResults(); newResults.begin(); for (PeakResult peakResult : results.getResults()) { float x = peakResult.params[Gaussian2DFunction.X_POSITION]; float y = peakResult.params[Gaussian2DFunction.Y_POSITION]; if (x < minX || x > maxX || y < minY || y > maxY) continue; newResults.add(peakResult); } newResults.end(); newResults.copySettings(results); newResults.setBounds(new Rectangle((int) minX, (int) minY, (int) (maxX - minX), (int) (maxY - minY))); return newResults; } /** * Gets the input filename that will be used in {@link ResultsManager#loadInputResults(String, boolean)}. * * @return the input filename */ static String getInputFilename() { return inputFilename; } /** * Sets the input filename that will be used in {@link ResultsManager#loadInputResults(String, boolean)}. * * @param inputFilename * the new input filename */ static void setInputFilename(String inputFilename) { ResultsManager.inputFilename = inputFilename; } }