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.ij.settings.FilterSettings; import gdsc.smlm.ij.settings.GlobalSettings; import gdsc.smlm.ij.settings.SettingsManager; import gdsc.core.ij.Utils; import gdsc.core.match.ClassificationResult; import gdsc.core.utils.UnicodeReader; import gdsc.smlm.results.MemoryPeakResults; import gdsc.smlm.results.PeakResult; import gdsc.smlm.results.PeakResultsReader; import gdsc.smlm.results.filter.AndFilter; import gdsc.smlm.results.filter.Filter; import gdsc.smlm.results.filter.FilterSet; import gdsc.smlm.results.filter.OrFilter; import gdsc.smlm.results.filter.PrecisionFilter; import gdsc.smlm.results.filter.PrecisionHysteresisFilter; import gdsc.smlm.results.filter.SNRFilter; import gdsc.smlm.results.filter.SNRHysteresisFilter; import gdsc.smlm.results.filter.TraceFilter; import gdsc.smlm.results.filter.WidthFilter; import gdsc.smlm.results.filter.XStreamWrapper; import ij.IJ; import ij.gui.GenericDialog; import ij.gui.Plot2; import ij.gui.PlotWindow; import ij.io.OpenDialog; import ij.plugin.PlugIn; import ij.plugin.WindowOrganiser; import ij.text.TextWindow; import java.awt.Color; import java.awt.Component; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FilenameFilter; import java.io.IOException; import java.io.OutputStreamWriter; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; /** * Run different filtering methods on a set of labelled peak results outputting performance statistics on the success of * the filter. * <p> * All results files in a specified directory are read. If the peak result original value is set to 1 it is considered a * true peak, 0 for a false peak. Filtering is done using e.g. SNR threshold, Precision thresholds, etc. The statistics * reported are shown in a table, e.g. precision, Jaccard, F-score. */ public class FilterAnalysis implements PlugIn { private static final String TITLE = "Filter Analysis"; private static TextWindow resultsWindow = null; private static TextWindow sensitivityWindow = null; private static boolean saveFilterSets = false; private static boolean showResultsTable = true; private static int plotTopN = 0; private ArrayList<NamedPlot> plots; private static boolean calculateSensitivity = false; private static double delta = 0.1; private HashMap<String, FilterScore> bestFilter; private LinkedList<String> bestFilterOrder; private static boolean snrFilter = true; private static int minSnr = 20; private static int maxSnr = 80; private static double minWidth = 1.5; private static double maxWidth = 2.0; private static double incWidth = 0.5; private static boolean precisionFilter = true; private static int minPrecision = 20; private static int maxPrecision = 80; private static boolean traceFilter = false; private static double minDistance = 0.3; private static double maxDistance = 1.2; private static double incDistance = 0.3; private static int minTime = 1; private static int maxTime = 80; private static int incTime = 10; private static boolean hysteresisSnrFilter = true; private static int minSnrGap = 10; private static int maxSnrGap = 40; private static int incSnrGap = 10; private static boolean hysteresisPrecisionFilter = true; private static int minPrecisionGap = 10; private static int maxPrecisionGap = 40; private static int incPrecisionGap = 10; private static List<MemoryPeakResults> resultsList = null; private String inputDirectory; private static String lastInputDirectory = ""; private boolean isHeadless; public FilterAnalysis() { isHeadless = java.awt.GraphicsEnvironment.isHeadless(); } /* * (non-Javadoc) * * @see ij.plugin.PlugIn#run(java.lang.String) */ public void run(String arg) { SMLMUsageTracker.recordPlugin(this.getClass(), arg); if (getInputDirectory() == null) return; resultsList = readResults(); if (resultsList.isEmpty()) { IJ.error(TITLE, "No results could be loaded. Check files have the suffix .xls, .csv or .bin"); return; } // Load filters from file or generate from dialog input List<FilterSet> filterSets = null; boolean fileInput = (arg != null && arg.contains("file")); if (!showDialog(resultsList, fileInput)) return; if (fileInput) { filterSets = readFilterSets(); if (filterSets == null) return; } else { filterSets = createFilters(); } if (filterSets == null || filterSets.isEmpty()) { IJ.error(TITLE, "No filters specified"); return; } if (!fileInput && saveFilterSets) saveFilterSets(filterSets); analyse(resultsList, filterSets); } private String getInputDirectory() { GlobalSettings gs = SettingsManager.loadSettings(); FilterSettings filterSettings = gs.getFilterSettings(); if (filterSettings.filterAnalysisDirectory != null) OpenDialog.setDefaultDirectory(filterSettings.filterAnalysisDirectory); filterSettings.filterAnalysisDirectory = IJ.getDirectory("Select results directory ..."); if (filterSettings.filterAnalysisDirectory == null) return null; SettingsManager.saveSettings(gs); return inputDirectory = filterSettings.filterAnalysisDirectory; } @SuppressWarnings("unchecked") private List<FilterSet> readFilterSets() { GlobalSettings gs = SettingsManager.loadSettings(); FilterSettings filterSettings = gs.getFilterSettings(); String[] path = Utils.decodePath(filterSettings.filterSetFilename); OpenDialog chooser = new OpenDialog("Filter_File", path[0], path[1]); if (chooser.getFileName() != null) { IJ.showStatus("Reading filters ..."); filterSettings.filterSetFilename = chooser.getDirectory() + chooser.getFileName(); BufferedReader input = null; try { FileInputStream fis = new FileInputStream(filterSettings.filterSetFilename); input = new BufferedReader(new UnicodeReader(fis, null)); Object o = XStreamWrapper.getInstance().fromXML(input); if (o != null && o instanceof List<?>) { SettingsManager.saveSettings(gs); return (List<FilterSet>) o; } IJ.log("No filter sets defined in the specified file: " + filterSettings.filterSetFilename); } catch (Exception e) { IJ.log("Unable to load the filter sets from file: " + e.getMessage()); } finally { if (input != null) { try { input.close(); } catch (IOException e) { // Ignore } } IJ.showStatus(""); } } return null; } private void saveFilterSets(List<FilterSet> filterSets) { GlobalSettings gs = SettingsManager.loadSettings(); FilterSettings filterSettings = gs.getFilterSettings(); String[] path = Utils.decodePath(filterSettings.filterSetFilename); OpenDialog chooser = new OpenDialog("Filter_File", path[0], path[1]); if (chooser.getFileName() != null) { filterSettings.filterSetFilename = chooser.getDirectory() + chooser.getFileName(); OutputStreamWriter out = null; try { FileOutputStream fos = new FileOutputStream(filterSettings.filterSetFilename); out = new OutputStreamWriter(fos, "UTF-8"); XStreamWrapper.getInstance().toXML(filterSets, out); SettingsManager.saveSettings(gs); } catch (Exception e) { IJ.log("Unable to save the filter sets to file: " + e.getMessage()); } finally { if (out != null) { try { out.close(); } catch (IOException e) { // Ignore } } } } } private List<MemoryPeakResults> readResults() { if (resultsList != null && inputDirectory.equals(lastInputDirectory)) { GenericDialog gd = new GenericDialog(TITLE); gd.addMessage("Re-use results from the same directory (no to refresh)?"); gd.enableYesNoCancel(); gd.hideCancelButton(); gd.showDialog(); if (gd.wasOKed()) return resultsList; } List<MemoryPeakResults> resultsList = new LinkedList<MemoryPeakResults>(); File[] fileList = (new File(inputDirectory)).listFiles(new FilenameFilter() { public boolean accept(File dir, String name) { return (name.endsWith(".xls") || name.endsWith(".csv") || name.endsWith(".bin")); } }); if (fileList != null) { // Exclude directories for (int i = 0; i < fileList.length; i++) { if (fileList[i].isFile()) { IJ.showStatus(String.format("Reading results ... %d/%d", i + 1, fileList.length)); IJ.showProgress(i, fileList.length); PeakResultsReader reader = new PeakResultsReader(fileList[i].getPath()); MemoryPeakResults results = reader.getResults(); if (results != null && results.size() > 0) { resultsList.add(results); } } } } IJ.showStatus(""); IJ.showProgress(1); lastInputDirectory = inputDirectory; return resultsList; } private boolean showDialog(List<MemoryPeakResults> resultsList, boolean fileInput) { GenericDialog gd = new GenericDialog(TITLE); gd.addHelp(About.HELP_URL); int total = 0; int tp = 0; for (MemoryPeakResults r : resultsList) { total += r.size(); for (PeakResult p : r.getResults()) if (p.origValue != 0) tp++; } gd.addMessage(String.format("%d files, %d results, %d True-Positives", resultsList.size(), total, tp)); if (!fileInput) { gd.addCheckbox("SNR_filter", snrFilter); gd.addNumericField("Min_SNR", minSnr, 0); gd.addNumericField("Max_SNR", maxSnr, 0); gd.addNumericField("Min_Width", minWidth, 2); gd.addNumericField("Max_Width", maxWidth, 2); gd.addNumericField("Increment_Width", incWidth, 2); gd.addCheckbox("Precision_filter", precisionFilter); gd.addNumericField("Min_Precision", minPrecision, 0); gd.addNumericField("Max_Precision", maxPrecision, 0); gd.addCheckbox("Trace_filter", traceFilter); gd.addNumericField("Min_distance", minDistance, 2); gd.addNumericField("Max_distance", maxDistance, 2); gd.addNumericField("Increment_distance", incDistance, 2); gd.addNumericField("Min_time", minTime, 0); gd.addNumericField("Max_time", maxTime, 0); gd.addNumericField("Increment_time", incTime, 0); gd.addCheckbox("Hysteresis_SNR_filter", hysteresisSnrFilter); gd.addNumericField("Min_SNR_gap", minSnrGap, 0); gd.addNumericField("Max_SNR_gap", maxSnrGap, 0); gd.addNumericField("Increment_SNR_gap", incSnrGap, 0); gd.addCheckbox("Hysteresis_Precision_filter", hysteresisPrecisionFilter); gd.addNumericField("Min_Precision_gap", minPrecisionGap, 0); gd.addNumericField("Max_Precision_gap", maxPrecisionGap, 0); gd.addNumericField("Increment_Precision_gap", incPrecisionGap, 0); gd.addCheckbox("Save_filters", saveFilterSets); } gd.addCheckbox("Show_table", showResultsTable); gd.addSlider("Plot_top_n", 0, 20, plotTopN); gd.addCheckbox("Calculate_sensitivity", calculateSensitivity); gd.addSlider("Delta", 0.01, 1, delta); if (!fileInput) { // Re-arrange to 2 columns if (gd.getLayout() != null) { GridBagLayout grid = (GridBagLayout) gd.getLayout(); Component splitLabel = (Component) gd.getCheckboxes().get(3); int xOffset = 0, yOffset = 0; int lastY = -1, rowCount = 0; for (Component comp : gd.getComponents()) { // Check if this should be the second major column if (comp == splitLabel) { xOffset += 2; yOffset -= rowCount; rowCount = 0; } // Reposition the field GridBagConstraints c = grid.getConstraints(comp); if (lastY != c.gridy) rowCount++; lastY = c.gridy; c.gridx = c.gridx + xOffset; c.gridy = c.gridy + yOffset; c.insets.left = c.insets.left + 10 * xOffset; c.insets.top = 0; c.insets.bottom = 0; grid.setConstraints(comp, c); } if (IJ.isLinux()) gd.setBackground(new Color(238, 238, 238)); } } gd.showDialog(); if (gd.wasCanceled() || !readDialog(gd, fileInput)) return false; return true; } private boolean readDialog(GenericDialog gd, boolean fileInput) { if (!fileInput) { snrFilter = gd.getNextBoolean(); minSnr = (int) gd.getNextNumber(); maxSnr = (int) gd.getNextNumber(); minWidth = gd.getNextNumber(); maxWidth = gd.getNextNumber(); incWidth = gd.getNextNumber(); precisionFilter = gd.getNextBoolean(); minPrecision = (int) gd.getNextNumber(); maxPrecision = (int) gd.getNextNumber(); traceFilter = gd.getNextBoolean(); minDistance = gd.getNextNumber(); maxDistance = gd.getNextNumber(); incDistance = gd.getNextNumber(); minTime = (int) gd.getNextNumber(); maxTime = (int) gd.getNextNumber(); incTime = (int) gd.getNextNumber(); hysteresisSnrFilter = gd.getNextBoolean(); minSnrGap = (int) gd.getNextNumber(); maxSnrGap = (int) gd.getNextNumber(); incSnrGap = (int) gd.getNextNumber(); hysteresisPrecisionFilter = gd.getNextBoolean(); minPrecisionGap = (int) gd.getNextNumber(); maxPrecisionGap = (int) gd.getNextNumber(); incPrecisionGap = (int) gd.getNextNumber(); saveFilterSets = gd.getNextBoolean(); } showResultsTable = gd.getNextBoolean(); plotTopN = (int) Math.abs(gd.getNextNumber()); calculateSensitivity = gd.getNextBoolean(); delta = gd.getNextNumber(); // Check there is one output if (!showResultsTable && !calculateSensitivity && plotTopN < 1) { IJ.error(TITLE, "No output selected"); return false; } // Check arguments try { if (!fileInput) { Parameters.isPositive("Min SNR", minSnr); Parameters.isAboveZero("Max SNR", maxSnr); Parameters.isPositive("Min width", minWidth); Parameters.isAboveZero("Max width", maxWidth); Parameters.isAboveZero("Increment width", incWidth); Parameters.isPositive("Min precision", minPrecision); Parameters.isAboveZero("Max precision", maxPrecision); Parameters.isPositive("Min Distance", minDistance); Parameters.isAboveZero("Max Distance", maxDistance); Parameters.isAboveZero("Increment Distance", incDistance); Parameters.isPositive("Min Time", minTime); Parameters.isAboveZero("Max Time", maxTime); Parameters.isAboveZero("Increment Time", incTime); Parameters.isPositive("Min Snr Gap", minSnrGap); Parameters.isAboveZero("Max Snr Gap", maxSnrGap); Parameters.isAboveZero("Increment Snr Gap", incSnrGap); Parameters.isPositive("Min Precision Gap", minPrecisionGap); Parameters.isAboveZero("Max Precision Gap", maxPrecisionGap); Parameters.isAboveZero("Increment Precision Gap", incPrecisionGap); } Parameters.isAboveZero("Delta", delta); Parameters.isBelow("Delta", delta, 1); } catch (IllegalArgumentException e) { IJ.error(TITLE, e.getMessage()); return false; } return !gd.invalidNumber(); } private List<FilterSet> createFilters() { IJ.showStatus("Creating filters ..."); List<FilterSet> filterSets = new LinkedList<FilterSet>(); addSNRFilters(filterSets); addPrecisionFilters(filterSets); addTraceFilters(filterSets); addSNRHysteresisFilters(filterSets); addPrecisionHysteresisFilters(filterSets); IJ.showStatus(""); return filterSets; } private void addSNRFilters(List<FilterSet> filterSets) { if (!snrFilter) return; for (double w = minWidth; w <= maxWidth; w += incWidth) { WidthFilter wf = new WidthFilter((float) w); List<Filter> filters = new LinkedList<Filter>(); for (int snr = minSnr; snr <= maxSnr; snr++) { filters.add(new AndFilter(wf, new SNRFilter(snr))); } filterSets.add(new FilterSet(filters)); } } private void addPrecisionFilters(List<FilterSet> filterSets) { if (!precisionFilter) return; List<Filter> filters = new LinkedList<Filter>(); for (int p = minPrecision; p <= maxPrecision; p++) { filters.add(new PrecisionFilter(p)); } filterSets.add(new FilterSet(filters)); } private void addTraceFilters(List<FilterSet> filterSets) { if (!traceFilter) return; for (double d = minDistance; d <= maxDistance; d += incDistance) { SNRFilter snr = new SNRFilter(maxSnr); List<Filter> filters = new LinkedList<Filter>(); for (int t = minTime; t <= maxTime; t += incTime) { filters.add(new OrFilter(snr, new TraceFilter(d, t))); } filterSets.add(new FilterSet(filters)); } } private void addSNRHysteresisFilters(List<FilterSet> filterSets) { if (!hysteresisSnrFilter) return; for (double w = minWidth; w <= maxWidth; w += incWidth) { WidthFilter wf = new WidthFilter((float) w); for (int snrGap = minSnrGap; snrGap <= maxSnrGap; snrGap += incSnrGap) { List<Filter> filters = new LinkedList<Filter>(); for (int snr = minSnr; snr <= maxSnr; snr++) { filters.add(new AndFilter(wf, new SNRHysteresisFilter(2, 0, 1, 0, snr, snrGap))); } filterSets.add(new FilterSet(filters)); } } } private void addPrecisionHysteresisFilters(List<FilterSet> filterSets) { if (!hysteresisPrecisionFilter) return; for (int precisionGap = minPrecisionGap; precisionGap <= maxPrecisionGap; precisionGap += incPrecisionGap) { List<Filter> filters = new LinkedList<Filter>(); for (int precision = minPrecision; precision <= maxPrecision; precision++) { filters.add(new PrecisionHysteresisFilter(2, 0, 1, 0, precision, precisionGap)); } filterSets.add(new FilterSet(filters)); } } /** * Run different filtering methods on a set of labelled peak results outputting performance statistics on the * success of * the filter to an ImageJ table. * <p> * If the peak result original value is set to 1 it is considered a true peak, 0 for a false peak. Filtering is done * using e.g. SNR threshold, Precision thresholds, etc. The statistics reported are shown in a table, e.g. * precision, Jaccard, F-score. * <p> * For each filter set a plot is shown of the Jaccard score verses the filter value, thus filters should be provided * in ascending numerical order otherwise they are sorted. * * @param resultsList * @param filterSets */ public void analyse(List<MemoryPeakResults> resultsList, List<FilterSet> filterSets) { createResultsWindow(); plots = new ArrayList<NamedPlot>(plotTopN); bestFilter = new HashMap<String, FilterScore>(); bestFilterOrder = new LinkedList<String>(); IJ.showStatus("Analysing filters ..."); int total = countFilters(filterSets); int count = 0; for (FilterSet filterSet : filterSets) { IJ.showStatus("Analysing " + filterSet.getName() + " ..."); count = run(filterSet, resultsList, count, total); } IJ.showProgress(1); IJ.showStatus(""); showPlots(); calculateSensitivity(resultsList); } private int countFilters(List<FilterSet> filterSets) { int count = 0; for (FilterSet filterSet : filterSets) count += filterSet.size(); return count; } private void showPlots() { if (plots.isEmpty()) return; // Display the top N plots int[] list = new int[plots.size()]; int i = 0; for (NamedPlot p : plots) { Plot2 plot = new Plot2(p.name, p.xAxisName, "Jaccard", p.xValues, p.yValues); plot.setLimits(p.xValues[0], p.xValues[p.xValues.length - 1], 0, 1); plot.setColor(Color.RED); plot.draw(); plot.setColor(Color.BLUE); plot.addPoints(p.xValues, p.yValues, Plot2.CROSS); PlotWindow plotWindow = Utils.display(p.name, plot); list[i++] = plotWindow.getImagePlus().getID(); } new WindowOrganiser().tileWindows(list); } private void calculateSensitivity(List<MemoryPeakResults> resultsList) { if (!calculateSensitivity) return; if (!bestFilter.isEmpty()) { IJ.showStatus("Calculating sensitivity ..."); createSensitivityWindow(); int currentIndex = 0; for (String type : bestFilterOrder) { IJ.showProgress(currentIndex++, bestFilter.size()); Filter filter = bestFilter.get(type).filter; ClassificationResult s = filter.score(resultsList); String message = type + "\t\t\t" + Utils.rounded(s.getJaccard(), 4) + "\t\t" + Utils.rounded(s.getPrecision(), 4) + "\t\t" + Utils.rounded(s.getRecall(), 4); if (isHeadless) { IJ.log(message); } else { sensitivityWindow.append(message); } // List all the parameters that can be adjusted. final int parameters = filter.getNumberOfParameters(); for (int index = 0; index < parameters; index++) { // For each parameter compute as upward + downward delta and get the average gradient Filter higher = filter.adjustParameter(index, delta); Filter lower = filter.adjustParameter(index, -delta); ClassificationResult sHigher = higher.score(resultsList); ClassificationResult sLower = lower.score(resultsList); StringBuilder sb = new StringBuilder(); sb.append("\t").append(filter.getParameterName(index)).append("\t"); sb.append(Utils.rounded(filter.getParameterValue(index), 4)).append("\t"); double dx1 = higher.getParameterValue(index) - filter.getParameterValue(index); double dx2 = filter.getParameterValue(index) - lower.getParameterValue(index); addSensitivityScore(sb, s.getJaccard(), sHigher.getJaccard(), sLower.getJaccard(), dx1, dx2); addSensitivityScore(sb, s.getPrecision(), sHigher.getPrecision(), sLower.getPrecision(), dx1, dx2); addSensitivityScore(sb, s.getRecall(), sHigher.getRecall(), sLower.getRecall(), dx1, dx2); if (isHeadless) { IJ.log(sb.toString()); } else { sensitivityWindow.append(sb.toString()); } } } String message = "-=-=-=-"; if (isHeadless) { IJ.log(message); } else { sensitivityWindow.append(message); } IJ.showProgress(1); IJ.showStatus(""); } } private void addSensitivityScore(StringBuilder sb, double s, double s1, double s2, double dx1, double dx2) { // Use absolute in case this is not a local maximum. We are mainly interested in how // flat the curve is at this point in relation to parameter changes. double abs1 = Math.abs(s - s1); double abs2 = Math.abs(s - s2); double dydx1 = (abs1) / dx1; double dydx2 = (abs2) / dx2; double relativeSensitivity = (abs1 + abs2) * 0.5; double sensitivity = (dydx1 + dydx2) * 0.5; sb.append(Utils.rounded(relativeSensitivity, 4)).append("\t"); sb.append(Utils.rounded(sensitivity, 4)).append("\t"); } private void createResultsWindow() { if (!showResultsTable) return; if (isHeadless) { IJ.log(createResultsHeader()); } else { if (resultsWindow == null || !resultsWindow.isShowing()) { String header = createResultsHeader(); resultsWindow = new TextWindow(TITLE + " Results", header, "", 900, 300); } } } private String createResultsHeader() { StringBuilder sb = new StringBuilder(); sb.append("Name\t"); sb.append("N\t"); sb.append("TP\t"); sb.append("FP\t"); sb.append("TN\t"); sb.append("FN\t"); sb.append("Jaccard\t"); sb.append("Precision\t"); sb.append("Recall\t"); sb.append("F1"); return sb.toString(); } private void createSensitivityWindow() { if (isHeadless) { IJ.log(createSensitivityHeader()); } else { if (sensitivityWindow == null || !sensitivityWindow.isShowing()) { String header = createSensitivityHeader(); sensitivityWindow = new TextWindow(TITLE + " Sensitivity", header, "", 900, 300); } } } private String createSensitivityHeader() { StringBuilder sb = new StringBuilder(); sb.append("Filter\t"); sb.append("Param\t"); sb.append("Value\t"); sb.append("J Sensitivity (delta)\t"); sb.append("J Sensitivity (unit)\t"); sb.append("P Sensitivity (delta)\t"); sb.append("P Sensitivity (unit)\t"); sb.append("R Sensitivity (delta)\t"); sb.append("R Sensitivity (unit)\t"); return sb.toString(); } private int run(FilterSet filterSet, List<MemoryPeakResults> resultsList, int count, final int total) { double[] xValues = (isHeadless) ? null : new double[filterSet.size()]; double[] yValues = (isHeadless) ? null : new double[filterSet.size()]; int i = 0; filterSet.sort(); // Track if all the filters are the same type. If so then we can calculate the sensitivity of each parameter. String type = null; boolean allSameType = true; Filter maxFilter = null; double maxScore = -1; for (Filter filter : filterSet.getFilters()) { if (count++ % 16 == 0) IJ.showProgress(count, total); ClassificationResult s = run(filter, resultsList); if (type == null) type = filter.getType(); else if (!type.equals(filter.getType())) allSameType = false; final double jaccard = s.getJaccard(); if (filter == null || maxScore < jaccard) { maxScore = jaccard; maxFilter = filter; } if (!isHeadless) { xValues[i] = filter.getNumericalValue(); yValues[i++] = jaccard; } } if (allSameType && calculateSensitivity) { FilterScore filterScore = bestFilter.get(type); if (filterScore != null) { if (filterScore.score < maxScore) filterScore.update(maxFilter, maxScore); } else { bestFilter.put(type, new FilterScore(maxFilter, maxScore)); bestFilterOrder.add(type); } } // Add spacer at end of each result set if (isHeadless) { if (showResultsTable) IJ.log(""); } else { if (showResultsTable) resultsWindow.append(""); if (plotTopN > 0) { // Check the xValues are unique. Since the filters have been sorted by their // numeric value we only need to compare adjacent entries. boolean unique = true; for (int ii = 0; ii < xValues.length - 1; ii++) { if (xValues[ii] == xValues[ii + 1]) { unique = false; break; } } String xAxisName = filterSet.getValueName(); // Check the values all refer to the same property for (Filter filter : filterSet.getFilters()) { if (!xAxisName.equals(filter.getNumericalValueName())) { unique = false; break; } } if (!unique) { // If not unique then renumber them and use an arbitrary label xAxisName = "Filter"; for (int ii = 0; ii < xValues.length; ii++) xValues[ii] = ii + 1; } String title = filterSet.getName(); // Check if a previous filter set had the same name, update if necessary NamedPlot p = getNamedPlot(title); if (p == null) plots.add(new NamedPlot(title, xAxisName, xValues, yValues)); else p.updateValues(xAxisName, xValues, yValues); if (plots.size() > plotTopN) { Collections.sort(plots); p = plots.remove(plots.size() - 1); } } } return count; } private NamedPlot getNamedPlot(String title) { for (NamedPlot p : plots) if (p.name.equals(title)) return p; return null; } private double getMaximum(double[] values) { double max = values[0]; for (int i = 1; i < values.length; i++) { if (values[i] > max) { max = values[i]; } } return max; } private ClassificationResult run(Filter filter, List<MemoryPeakResults> resultsList) { ClassificationResult s = filter.score(resultsList); if (showResultsTable) { StringBuilder sb = new StringBuilder(); sb.append(filter.getName()).append("\t"); sb.append(s.getTP()+s.getFP()).append("\t"); sb.append(s.getTP()).append("\t"); sb.append(s.getFP()).append("\t"); sb.append(s.getTN()).append("\t"); sb.append(s.getFN()).append("\t"); sb.append(s.getJaccard()).append("\t"); sb.append(s.getPrecision()).append("\t"); sb.append(s.getRecall()).append("\t"); sb.append(s.getF1Score()); if (isHeadless) { IJ.log(sb.toString()); } else { resultsWindow.append(sb.toString()); } } return s; } public class FilterScore { Filter filter; double score; public FilterScore(Filter filter, double score) { update(filter, score); } public void update(Filter filter, double score) { this.filter = filter; this.score = score; } } public class NamedPlot implements Comparable<NamedPlot> { String name, xAxisName; double[] xValues, yValues; double score; public NamedPlot(String name, String xAxisName, double[] xValues, double[] yValues) { this.name = name; updateValues(xAxisName, xValues, yValues); } public void updateValues(String xAxisName, double[] xValues, double[] yValues) { this.xAxisName = xAxisName; this.xValues = xValues; this.yValues = yValues; this.score = getMaximum(yValues); } public int compareTo(NamedPlot o) { if (score > o.score) return -1; if (score < o.score) return 1; return 0; } } }