package gdsc.smlm.results.filter;
import gdsc.smlm.ga.Chromosome;
import gdsc.core.ij.Utils;
import gdsc.core.match.ClassificationResult;
import gdsc.core.match.FractionClassificationResult;
import gdsc.smlm.results.PeakResult;
import gdsc.smlm.results.MemoryPeakResults;
import java.util.List;
import org.apache.commons.math3.util.FastMath;
import com.thoughtworks.xstream.annotations.XStreamOmitField;
/*-----------------------------------------------------------------------------
* GDSC SMLM Software
*
* Copyright (C) 2015 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.
*---------------------------------------------------------------------------*/
/**
* Filter a set of peak results into accepted/rejected.
*/
public abstract class Filter implements Comparable<Filter>, Chromosome<FilterScore>, Cloneable
{
@XStreamOmitField
private String name;
@XStreamOmitField
private String type;
@XStreamOmitField
private FilterScore fitness;
/**
* Generate the name of the filter using the filter settings (defaults to the first parameter)
*
* @return The name of the filter
*/
protected String generateName()
{
return getParameterName(0) + " " + getParameterValue(0);
}
/**
* Generate the type of the filter using the filter settings (default to the class name with 'Filter' removed)
*
* @return The type of the filter
*/
protected String generateType()
{
return this.getClass().getSimpleName().replaceAll("Filter", "");
}
/**
* Filter the results
*
* @param results
* @return the filtered results
*/
public MemoryPeakResults filter(MemoryPeakResults results)
{
MemoryPeakResults newResults = new MemoryPeakResults();
newResults.copySettings(results);
setup(results);
for (PeakResult peak : results.getResults())
{
if (accept(peak))
newResults.add(peak);
}
end();
return newResults;
}
/**
* Filter the results
* <p>
* The number of consecutive rejections are counted per frame. When the configured number of failures is reached all
* remaining results for the frame are rejected. This assumes the results are ordered by the frame.
*
* @param results
* @param failures
* the number of failures to allow per frame before all peaks are rejected
* @return the filtered results
*/
public MemoryPeakResults filter(MemoryPeakResults results, int failures)
{
MemoryPeakResults newResults = new MemoryPeakResults();
newResults.copySettings(results);
setup(results);
int frame = -1;
int failCount = 0;
for (PeakResult peak : results.getResults())
{
if (frame != peak.getFrame())
{
frame = peak.getFrame();
failCount = 0;
}
// Reject all peaks if we have exceeded the fail count
final boolean isPositive;
if (failCount > failures)
{
isPositive = false;
}
else
{
// Otherwise assess the peak
isPositive = accept(peak);
}
if (isPositive)
{
failCount = 0;
newResults.add(peak);
}
else
{
failCount++;
}
}
end();
return newResults;
}
/**
* Filter the results
* <p>
* The number of consecutive rejections are counted per frame. When the configured number of failures is reached all
* remaining results for the frame are rejected. This assumes the results are ordered by the frame.
* <p>
* Note that this method is to be used to score a set of results that may have been extracted from a larger set
* since the number of consecutive failures before each peak are expected to be stored in the origY property. Set
* this to zero and the results should be identical to {@link #filter(MemoryPeakResults, int)}
*
* @param results
* @param failures
* the number of failures to allow per frame before all peaks are rejected
* @return the filtered results
*/
public MemoryPeakResults filter2(MemoryPeakResults results, int failures)
{
MemoryPeakResults newResults = new MemoryPeakResults();
newResults.copySettings(results);
setup(results);
int frame = -1;
int failCount = 0;
for (PeakResult peak : results.getResults())
{
if (frame != peak.getFrame())
{
frame = peak.getFrame();
failCount = 0;
}
failCount += peak.origY;
// Reject all peaks if we have exceeded the fail count
final boolean isPositive;
if (failCount > failures)
{
isPositive = false;
}
else
{
// Otherwise assess the peak
isPositive = accept(peak);
}
if (isPositive)
{
failCount = 0;
newResults.add(peak);
}
else
{
failCount++;
}
}
end();
return newResults;
}
/**
* Filter the results
* <p>
* Input PeakResults must be allocated a score for true positive, false positive, true negative and false negative
* (accessed via the object property get methods). The filter is run and results that pass accumulate scores for
* true positive and false positive, otherwise the scores are accumulated for true negative and false negative. The
* simplest scoring scheme is to mark valid results as tp=fn=1 and fp=tn=0 and invalid results the opposite.
* <p>
* The number of consecutive rejections are counted per frame. When the configured number of failures is reached all
* remaining results for the frame are rejected. This assumes the results are ordered by the frame.
* <p>
* The number of failures before each peak is stored in the origX property of the PeakResult.
*
* @param results
* @param score
* If not null will be populated with the fraction score [ tp, fp, tn, fn, p, n ]
* @return the filtered results
*/
public MemoryPeakResults filterSubset(MemoryPeakResults results, double[] score)
{
MemoryPeakResults newResults = new MemoryPeakResults();
newResults.copySettings(results);
setup(results);
int frame = -1;
int failCount = 0;
double fp = 0, fn = 0;
double tp = 0, tn = 0;
int p = 0;
for (PeakResult peak : results.getResults())
{
if (frame != peak.getFrame())
{
frame = peak.getFrame();
failCount = 0;
}
// Reject all peaks if we have exceeded the fail count
final boolean isPositive = accept(peak);
if (isPositive)
{
peak.origX = failCount;
failCount = 0;
newResults.add(peak);
}
else
{
failCount++;
}
if (isPositive)
{
p++;
tp += peak.getTruePositiveScore();
fp += peak.getFalsePositiveScore();
}
else
{
fn += peak.getFalseNegativeScore();
tn += peak.getTrueNegativeScore();
}
}
end();
if (score != null && score.length > 5)
{
score[0] = tp;
score[1] = fp;
score[2] = tn;
score[3] = fn;
score[4] = p;
score[5] = results.size() - p;
}
return newResults;
}
/**
* Filter the results
* <p>
* Input PeakResults must be allocated a score for true positive, false positive, true negative and false negative
* (accessed via the object property get methods). The filter is run and results that pass accumulate scores for
* true positive and false positive, otherwise the scores are accumulated for true negative and false negative. The
* simplest scoring scheme is to mark valid results as tp=fn=1 and fp=tn=0 and invalid results the opposite.
* <p>
* The number of consecutive rejections are counted per frame. When the configured number of failures is reached all
* remaining results for the frame are rejected. This assumes the results are ordered by the frame.
* <p>
* Note that this method is to be used to score a set of results that may have been extracted from a larger set
* since the number of consecutive failures before each peak are expected to be stored in the origY property. Set
* this to zero and the results should be identical to {@link #filterSubset(MemoryPeakResults, double[])}.
* <p>
* The number of failures before each peak is stored in the origX property of the PeakResult.
*
* @param results
* @param score
* If not null will be populated with the fraction score [ tp, fp, tn, fn, p, n ]
* @return the filtered results
*/
public MemoryPeakResults filterSubset2(MemoryPeakResults results, double[] score)
{
MemoryPeakResults newResults = new MemoryPeakResults();
newResults.copySettings(results);
setup(results);
int frame = -1;
int failCount = 0;
double fp = 0, fn = 0;
double tp = 0, tn = 0;
int p = 0;
for (PeakResult peak : results.getResults())
{
if (frame != peak.getFrame())
{
frame = peak.getFrame();
failCount = 0;
}
failCount += peak.origY;
// Reject all peaks if we have exceeded the fail count
final boolean isPositive = accept(peak);
if (isPositive)
{
peak.origX = failCount;
failCount = 0;
newResults.add(peak);
}
else
{
failCount++;
}
if (isPositive)
{
p++;
tp += peak.getTruePositiveScore();
fp += peak.getFalsePositiveScore();
}
else
{
fn += peak.getFalseNegativeScore();
tn += peak.getTrueNegativeScore();
}
}
end();
if (score != null && score.length > 5)
{
score[0] = tp;
score[1] = fp;
score[2] = tn;
score[3] = fn;
score[4] = p;
score[5] = results.size() - p;
}
return newResults;
}
/**
* Filter the results
* <p>
* Input PeakResults must be allocated a score for true positive, false positive, true negative and false negative
* (accessed via the object property get methods). The filter is run and results that pass accumulate scores for
* true positive and false positive, otherwise the scores are accumulated for true negative and false negative. The
* simplest scoring scheme is to mark valid results as tp=fn=1 and fp=tn=0 and invalid results the opposite.
* <p>
* The number of consecutive rejections are counted per frame. When the configured number of failures is reached all
* remaining results for the frame are rejected. This assumes the results are ordered by the frame.
* <p>
* The number of failures before each peak is stored in the origX property of the PeakResult.
*
* @param results
* @param failures
* the number of failures to allow per frame before all peaks are rejected
* @param score
* If not null will be populated with the fraction score [ tp, fp, tn, fn, p, n ]
* @return the filtered results
*/
public MemoryPeakResults filterSubset(MemoryPeakResults results, int failures, double[] score)
{
MemoryPeakResults newResults = new MemoryPeakResults();
newResults.copySettings(results);
setup(results);
int frame = -1;
int failCount = 0;
double fp = 0, fn = 0;
double tp = 0, tn = 0;
for (PeakResult peak : results.getResults())
{
if (frame != peak.getFrame())
{
frame = peak.getFrame();
failCount = 0;
}
// Reject all peaks if we have exceeded the fail count
final boolean isPositive;
if (failCount > failures)
{
isPositive = false;
}
else
{
// Otherwise assess the peak
isPositive = accept(peak);
}
if (isPositive)
{
peak.origX = failCount;
failCount = 0;
newResults.add(peak);
}
else
{
failCount++;
}
if (isPositive)
{
tp += peak.getTruePositiveScore();
fp += peak.getFalsePositiveScore();
}
else
{
fn += peak.getFalseNegativeScore();
tn += peak.getTrueNegativeScore();
}
}
end();
if (score != null && score.length > 5)
{
score[0] = tp;
score[1] = fp;
score[2] = tn;
score[3] = fn;
score[4] = newResults.size();
score[5] = results.size() - newResults.size();
}
return newResults;
}
/**
* Filter the results
* <p>
* Input PeakResults must be allocated a score for true positive, false positive, true negative and false negative
* (accessed via the object property get methods). The filter is run and results that pass accumulate scores for
* true positive and false positive, otherwise the scores are accumulated for true negative and false negative. The
* simplest scoring scheme is to mark valid results as tp=fn=1 and fp=tn=0 and invalid results the opposite.
* <p>
* The number of consecutive rejections are counted per frame. When the configured number of failures is reached all
* remaining results for the frame are rejected. This assumes the results are ordered by the frame.
* <p>
* Note that this method is to be used to score a set of results that may have been extracted from a larger set
* since the number of consecutive failures before each peak are expected to be stored in the origY property. Set
* this to zero and the results should be identical to {@link #filterSubset(MemoryPeakResults, int, double[])}.
* <p>
* The number of failures before each peak is stored in the origX property of the PeakResult.
*
* @param results
* @param failures
* the number of failures to allow per frame before all peaks are rejected
* @param score
* If not null will be populated with the fraction score [ tp, fp, tn, fn, p, n ]
* @return the filtered results
*/
public MemoryPeakResults filterSubset2(MemoryPeakResults results, int failures, double[] score)
{
MemoryPeakResults newResults = new MemoryPeakResults();
newResults.copySettings(results);
setup(results);
int frame = -1;
int failCount = 0;
double fp = 0, fn = 0;
double tp = 0, tn = 0;
for (PeakResult peak : results.getResults())
{
if (frame != peak.getFrame())
{
frame = peak.getFrame();
failCount = 0;
}
failCount += peak.origY;
// Reject all peaks if we have exceeded the fail count
final boolean isPositive;
if (failCount > failures)
{
isPositive = false;
}
else
{
// Otherwise assess the peak
isPositive = accept(peak);
}
if (isPositive)
{
peak.origX = failCount;
failCount = 0;
newResults.add(peak);
}
else
{
failCount++;
}
if (isPositive)
{
tp += peak.getTruePositiveScore();
fp += peak.getFalsePositiveScore();
}
else
{
fn += peak.getFalseNegativeScore();
tn += peak.getTrueNegativeScore();
}
}
end();
if (score != null && score.length > 5)
{
score[0] = tp;
score[1] = fp;
score[2] = tn;
score[3] = fn;
score[4] = newResults.size();
score[5] = results.size() - newResults.size();
}
return newResults;
}
/**
* Filter the results and return the performance score. Allows benchmarking the filter by marking the results as
* true or false.
* <p>
* Any input PeakResult with an original value that is not zero will be treated as a true result, all other results
* are false. The filter is run and the results are marked as true positive, false negative and false positive.
*
* @param resultsList
* a list of results to analyse
* @return the score
*/
public ClassificationResult score(List<MemoryPeakResults> resultsList)
{
int tp = 0, fp = 0, tn = 0, fn = 0;
for (MemoryPeakResults peakResults : resultsList)
{
setup(peakResults);
for (PeakResult peak : peakResults.getResults())
{
final boolean isTrue = peak.origValue != 0;
boolean isPositive = accept(peak);
if (isTrue)
{
if (isPositive)
tp++; // true positive
else
fn++; // false negative
}
else
{
if (isPositive)
fp++; // false positive
else
tn++; // true negative
}
}
end();
}
return new ClassificationResult(tp, fp, tn, fn);
}
/**
* Filter the results and return the performance score. Allows benchmarking the filter by marking the results as
* true or false.
* <p>
* Any input PeakResult with an original value that is not zero will be treated as a true result, all other results
* are false. The filter is run and the results are marked as true positive, false negative and false positive.
*
* @param resultsList
* a list of results to analyse
* @param tn
* The initial true negatives (used when the results have been pre-filtered)
* @param fn
* The initial false negatives (used when the results have been pre-filtered)
* @return
*/
public ClassificationResult score(List<MemoryPeakResults> resultsList, int tn, int fn)
{
int tp = 0, fp = 0;
for (MemoryPeakResults peakResults : resultsList)
{
setup(peakResults);
for (PeakResult peak : peakResults.getResults())
{
final boolean isTrue = peak.origValue != 0;
boolean isPositive = accept(peak);
if (isTrue)
{
if (isPositive)
tp++; // true positive
else
fn++; // false negative
}
else
{
if (isPositive)
fp++; // false positive
else
tn++; // true negative
}
}
end();
}
return new ClassificationResult(tp, fp, tn, fn);
}
/**
* Filter the results and return the performance score. Allows benchmarking the filter by marking the results as
* true or false.
* <p>
* Any input PeakResult with an original value that is not zero will be treated as a true result, all other results
* are false. The filter is run and the results are marked as true positive, false negative and false positive.
* <p>
* The number of consecutive rejections are counted per frame. When the configured number of failures is reached all
* remaining results for the frame are rejected. This assumes the results are ordered by the frame.
*
* @param resultsList
* a list of results to analyse
* @param failures
* the number of failures to allow per frame before all peaks are rejected
* @return the score
*/
public ClassificationResult score(List<MemoryPeakResults> resultsList, int failures)
{
int tp = 0, fp = 0, tn = 0, fn = 0;
for (MemoryPeakResults peakResults : resultsList)
{
setup(peakResults);
int frame = -1;
int failCount = 0;
for (PeakResult peak : peakResults.getResults())
{
final boolean isTrue = peak.origValue != 0;
// Reset fail count for new frames
if (frame != peak.getFrame())
{
frame = peak.getFrame();
failCount = 0;
}
// Reject all peaks if we have exceeded the fail count
final boolean isPositive;
if (failCount > failures)
{
isPositive = false;
}
else
{
// Otherwise assess the peak
isPositive = accept(peak);
}
if (isPositive)
{
failCount = 0;
}
else
{
failCount++;
}
if (isTrue)
{
if (isPositive)
tp++; // true positive
else
fn++; // false negative
}
else
{
if (isPositive)
fp++; // false positive
else
tn++; // true negative
}
}
end();
}
return new ClassificationResult(tp, fp, tn, fn);
}
/**
* Filter the results and return the performance score. Allows benchmarking the filter by marking the results as
* true or false.
* <p>
* Any input PeakResult with an original value that is not zero will be treated as a true result, all other results
* are false. The filter is run and the results are marked as true positive, false negative and false positive.
* <p>
* The number of consecutive rejections are counted per frame. When the configured number of failures is reached all
* remaining results for the frame are rejected. This assumes the results are ordered by the frame.
* <p>
* Note that this method is to be used to score a subset that was generated using
* {@link #filterSubset(MemoryPeakResults, int)} since the number of consecutive failures before each peak are
* expected to be stored in the origX property.
*
* @param resultsList
* a list of results to analyse
* @param failures
* the number of failures to allow per frame before all peaks are rejected
* @param tn
* The initial true negatives (used when the results have been pre-filtered)
* @param fn
* The initial false negatives (used when the results have been pre-filtered)
* @return the score
*/
public ClassificationResult scoreSubset(List<MemoryPeakResults> resultsList, int failures, int tn, int fn)
{
int tp = 0, fp = 0;
for (MemoryPeakResults peakResults : resultsList)
{
setup(peakResults);
int frame = -1;
int failCount = 0;
for (PeakResult peak : peakResults.getResults())
{
final boolean isTrue = peak.origValue != 0;
// Reset fail count for new frames
if (frame != peak.getFrame())
{
frame = peak.getFrame();
failCount = 0;
}
failCount += peak.origX;
// Reject all peaks if we have exceeded the fail count
final boolean isPositive;
if (failCount > failures)
{
isPositive = false;
}
else
{
// Otherwise assess the peak
isPositive = accept(peak);
}
if (isPositive)
{
failCount = 0;
}
else
{
failCount++;
}
if (isTrue)
{
if (isPositive)
tp++; // true positive
else
fn++; // false negative
}
else
{
if (isPositive)
fp++; // false positive
else
tn++; // true negative
}
}
end();
}
return new ClassificationResult(tp, fp, tn, fn);
}
/**
* Filter the results and return the performance score. Allows benchmarking the filter by marking the results as
* true or false.
* <p>
* Input PeakResults must be allocated a score for true positive, false positive, true negative and false negative
* (accessed via the object property get methods). The filter is run and results that pass accumulate scores for
* true positive and false positive, otherwise the scores are accumulated for true negative and false negative. The
* simplest scoring scheme is to mark valid results as tp=fn=1 and fp=tn=0 and invalid results the opposite.
* <p>
* The number of consecutive rejections are counted per frame. When the configured number of failures is reached all
* remaining results for the frame are rejected. This assumes the results are ordered by the frame.
*
* @param resultsList
* a list of results to analyse
* @param failures
* the number of failures to allow per frame before all peaks are rejected
* @return the score
*/
public FractionClassificationResult fractionScore(List<MemoryPeakResults> resultsList, int failures)
{
int p = 0, n = 0;
double fp = 0, fn = 0;
double tp = 0, tn = 0;
for (MemoryPeakResults peakResults : resultsList)
{
setup(peakResults);
int frame = -1;
int failCount = 0;
for (PeakResult peak : peakResults.getResults())
{
// Reset fail count for new frames
if (frame != peak.getFrame())
{
frame = peak.getFrame();
failCount = 0;
}
// Reject all peaks if we have exceeded the fail count
final boolean isPositive;
if (failCount > failures)
{
isPositive = false;
}
else
{
// Otherwise assess the peak
isPositive = accept(peak);
}
if (isPositive)
{
failCount = 0;
}
else
{
failCount++;
}
if (isPositive)
{
p++;
tp += peak.getTruePositiveScore();
fp += peak.getFalsePositiveScore();
}
else
{
fn += peak.getFalseNegativeScore();
tn += peak.getTrueNegativeScore();
}
}
n += peakResults.size();
end();
}
n -= p;
return new FractionClassificationResult(tp, fp, tn, fn, p, n);
}
/**
* Filter the results and return the performance score. Allows benchmarking the filter by marking the results as
* true or false.
* <p>
* Input PeakResults must be allocated a score for true positive, false positive, true negative and false negative
* (accessed via the object property get methods). The filter is run and results that pass accumulate scores for
* true positive and false positive, otherwise the scores are accumulated for true negative and false negative. The
* simplest scoring scheme is to mark valid results as tp=fn=1 and fp=tn=0 and invalid results the opposite.
* <p>
* The number of consecutive rejections are counted per frame. When the configured number of failures is reached all
* remaining results for the frame are rejected. This assumes the results are ordered by the frame.
* <p>
* Note that this method is to be used to score a set of results that may have been extracted from a larger set
* since the number of consecutive failures before each peak are expected to be stored in the origY property. Set
* this to zero and the results should be identical to {@link #fractionScore(List, int)}.
*
* @param resultsList
* a list of results to analyse
* @param failures
* the number of failures to allow per frame before all peaks are rejected
* @return the score
*/
public FractionClassificationResult fractionScore2(List<MemoryPeakResults> resultsList, int failures)
{
int p = 0, n = 0;
double fp = 0, fn = 0;
double tp = 0, tn = 0;
for (MemoryPeakResults peakResults : resultsList)
{
setup(peakResults);
int frame = -1;
int failCount = 0;
for (PeakResult peak : peakResults.getResults())
{
// Reset fail count for new frames
if (frame != peak.getFrame())
{
frame = peak.getFrame();
failCount = 0;
}
failCount += peak.origY;
// Reject all peaks if we have exceeded the fail count
final boolean isPositive;
if (failCount > failures)
{
isPositive = false;
}
else
{
// Otherwise assess the peak
isPositive = accept(peak);
}
if (isPositive)
{
failCount = 0;
}
else
{
failCount++;
}
if (isPositive)
{
p++;
tp += peak.getTruePositiveScore();
fp += peak.getFalsePositiveScore();
}
else
{
fn += peak.getFalseNegativeScore();
tn += peak.getTrueNegativeScore();
}
}
n += peakResults.size();
end();
}
n -= p;
return new FractionClassificationResult(tp, fp, tn, fn, p, n);
}
/**
* Filter the results and return the performance score. Allows benchmarking the filter by marking the results as
* true or false.
* <p>
* Input PeakResults must be allocated a score for true positive, false positive, true negative and false negative
* (accessed via the object property get methods). The filter is run and results that pass accumulate scores for
* true positive and false positive, otherwise the scores are accumulated for true negative and false negative. The
* simplest scoring scheme is to mark valid results as tp=fn=1 and fp=tn=0 and invalid results the opposite.
* <p>
* The number of consecutive rejections are counted per frame. When the configured number of failures is reached all
* remaining results for the frame are rejected. This assumes the results are ordered by the frame.
* <p>
* Note that this method is to be used to score a subset that was generated using
* {@link #filterSubset(MemoryPeakResults, int)} since the number of consecutive failures before each peak are
* expected to be stored in the origX property.
*
* @param resultsList
* a list of results to analyse
* @param failures
* the number of failures to allow per frame before all peaks are rejected
* @param tn
* The initial true negatives (used when the results have been pre-filtered)
* @param fn
* The initial false negatives (used when the results have been pre-filtered)
* @param n
* The initial negatives (used when the results have been pre-filtered)
* @return the score
*/
public FractionClassificationResult fractionScoreSubset(List<MemoryPeakResults> resultsList, int failures,
double tn, double fn, int n)
{
int p = 0;
double fp = 0;
double tp = 0;
for (MemoryPeakResults peakResults : resultsList)
{
setup(peakResults);
int frame = -1;
int failCount = 0;
for (PeakResult peak : peakResults.getResults())
{
// Reset fail count for new frames
if (frame != peak.getFrame())
{
frame = peak.getFrame();
failCount = 0;
}
failCount += peak.origX;
// Reject all peaks if we have exceeded the fail count
final boolean isPositive;
if (failCount > failures)
{
isPositive = false;
}
else
{
// Otherwise assess the peak
isPositive = accept(peak);
}
if (isPositive)
{
failCount = 0;
}
else
{
failCount++;
}
if (isPositive)
{
p++;
tp += peak.getTruePositiveScore();
fp += peak.getFalsePositiveScore();
}
else
{
fn += peak.getFalseNegativeScore();
tn += peak.getTrueNegativeScore();
}
}
n += peakResults.size();
end();
}
n -= p;
return new FractionClassificationResult(tp, fp, tn, fn, p, n);
}
/**
* Called before the accept method is called for each peak in the results. Allows pre-processing of the results.
*
* @param peakResults
*/
public abstract void setup(MemoryPeakResults peakResults);
/**
* Called for each peak in the results that are filtered.
*
* @param peak
* @return true if the peak should be accepted, otherwise false to reject.
*/
public abstract boolean accept(PeakResult peak);
/**
* Called after the accept method has been called for each peak in the results. Allows memory clean-up of the
* results.
*/
public void end()
{
}
/**
* The numerical value of the filter (defaults to the first parameter)
*
* @return The numerical value of the filter. Used for plotting value against performance score.
*/
public double getNumericalValue()
{
return getParameterValue(0);
}
/**
* The name of the numerical value of the filter (defaults to the first parameter)
*
* @return The name of the numerical value of the filter. Used for plotting value against performance score.
*/
public String getNumericalValueName()
{
return getParameterName(0);
}
/**
* @return the name (including any parameter values)
*/
public String getName()
{
if (name == null)
name = generateName();
return name;
}
/**
* @return the type (excluding any parameter values)
*/
public String getType()
{
if (type == null)
type = generateType();
return type;
}
/**
* @return Describes the functionality of the filter
*/
public abstract String getDescription();
/**
* @return An XML representation of this object
*/
public String toXML()
{
return XStreamWrapper.toXML(this);
}
/**
* Create the filter from the XML representation
*
* @param xml
* @return the filter
*/
public static Filter fromXML(String xml)
{
try
{
Filter f = (Filter) XStreamWrapper.fromXML(xml);
f.initialiseState();
return f;
}
catch (ClassCastException ex)
{
//ex.printStackTrace();
}
return null;
}
/**
* Run after the filter is deserialised using XStream or cloned. Overrride this method if the filter has state that
* requires resetting.
*/
protected void initialiseState()
{
}
/*
* (non-Javadoc)
*
* @see java.lang.Comparable#compareTo(java.lang.Object)
*/
public int compareTo(Filter o)
{
// Null to end of list
if (o == null)
return -1;
final double v1 = getNumericalValue();
final double v2 = o.getNumericalValue();
if (v1 < v2)
return -1;
if (v1 > v2)
return 1;
// Use all the parameters
final int size = getNumberOfParameters();
if (size == o.getNumberOfParameters())
{
for (int i = 0; i < size; i++)
{
final double d1 = getParameterValueInternal(i);
final double d2 = o.getParameterValueInternal(i);
if (d1 < d2)
return -1;
if (d1 > d2)
return 1;
}
}
return 0;
}
/**
* Compare to the other filter, count the number of weakest parameters. If negative then this filter has more weak
* parameters. If positive then this filter has less weak parameters. If the same or the number of parameters do not
* match then return 0. If the other filter is null return -1.
*
* @param o
* The other filter
* @return the count difference
*/
public int weakest(Filter o)
{
// Null to end of list
if (o == null)
return -1;
// Use all the parameters
int i = getNumberOfParameters();
if (i == o.getNumberOfParameters())
{
// Extract the parameters
final double[] p1 = getParameters();
final double[] p2 = o.getParameters();
// Find the weakest
final double[] weakest = p1.clone();
o.weakestParameters(weakest);
// Count the number of weakest
int c = 0;
while (i-- > 0)
{
if (p1[i] != p2[i])
{
if (p1[i] == weakest[i])
--c;
else
++c;
}
}
return c;
}
return 0;
}
/**
* Compare to the other filter, count the number of weakest parameters. If negative then this filter has more weak
* parameters. If positive then this filter has less weak parameters.
* <p>
* This method does not check for null or if the other filter has a different number of parameters.
*
* @param o
* The other filter
* @return the count difference
*/
public int weakestUnsafe(Filter o)
{
// Use all the parameters
int i = getNumberOfParameters();
// Extract the parameters
final double[] p1 = getParameters();
final double[] p2 = o.getParameters();
// Find the weakest
final double[] weakest = p1.clone();
o.weakestParameters(weakest);
// Count the number of weakest
int c = 0;
while (i-- > 0)
{
if (p1[i] != p2[i])
{
if (p1[i] == weakest[i])
--c;
else
++c;
}
}
return c;
}
/**
* @return The number of parameters for the filter
*/
public abstract int getNumberOfParameters();
protected void checkIndex(final int index)
{
if (index < 0 || index >= getNumberOfParameters())
throw new IndexOutOfBoundsException("Index must be >= 0 and < " + getNumberOfParameters());
}
/**
* Get the parameter value.
*
* @param index
* @return The value of the specified parameter
*/
public double getParameterValue(int index)
{
checkIndex(index);
return getParameterValueInternal(index);
}
/**
* Get the parameter value. The index should always be between 0 and {@link #getNumberOfParameters()}
*
* @param index
* @return The value of the specified parameter
*/
protected abstract double getParameterValueInternal(int index);
/**
* Gets the parameters as an array.
*
* @return the parameters
*/
public double[] getParameters()
{
final int n = getNumberOfParameters();
final double[] p = new double[n];
for (int i = 0; i < n; i++)
p[i] = getParameterValueInternal(i);
return p;
}
/**
* Get the recommended minimum amount by which to increment the parameter
*
* @param index
* @return The increment value of the specified parameter
*/
public abstract double getParameterIncrement(int index);
/**
* Return a value to use to disable the parameter
* <p>
* Override this method if zero does not disable the parameter
*
* @param index
* @return The disabled value of the specified parameter
*/
public double getDisabledParameterValue(int index)
{
checkIndex(index);
return 0;
}
/**
* @param index
* @return The name of the specified parameter
*/
public String getParameterName(int index)
{
return getParameterType(index).toString();
}
/**
* @param index
* @return The type of the specified parameter
*/
public abstract ParameterType getParameterType(int index);
/**
* Create a new filter by adjusting the specified parameter.
* <p>
* A positive delta will adjust the parameter to be larger. A negative delta will adjust the parameter to be
* smaller. The adjustment is relative to the parameter value, e.g. 0.1 is 10%.
* <p>
* Filters can adjust the parameter by a different amount, e.g. by the delta multiplied by a range expected to
* change the filter performance. This may be relevant in the case where the value is presently zero since no
* relative change is possible.
*
* @param index
* The parameter index
* @param delta
* The amount to adjust the parameter
* @return The new filter
*/
public abstract Filter adjustParameter(int index, double delta);
/**
* Adjust the specified parameter value.
* <p>
* A positive delta will adjust the parameter to be larger. A negative delta will adjust the parameter to be
* smaller. The adjustment is relative to the parameter value, e.g. 0.1 is 10%.
*
* @param value
* @param delta
* @param defaultRange
* The default range to apply the delta to in the case where the value is zero and no relative adjustment
* is possible.
* @return
*/
protected double updateParameter(double value, double delta, double defaultRange)
{
if (value != 0)
return (value + value * delta);
return (value + defaultRange * delta);
}
/**
* Adjust the specified parameter value.
* <p>
* A positive delta will adjust the parameter to be larger. A negative delta will adjust the parameter to be
* smaller. The adjustment is relative to the parameter value, e.g. 0.1 is 10%.
*
* @param value
* @param delta
* @param defaultRange
* The default range to apply the delta to in the case where the value is zero and no relative adjustment
* is possible.
* @return
*/
protected float updateParameter(float value, double delta, double defaultRange)
{
if (value != 0)
return (float) (value + value * delta);
return (float) (value + defaultRange * delta);
}
/**
* Adjust the specified parameter value.
* <p>
* A positive delta will adjust the parameter to be larger. A negative delta will adjust the parameter to be
* smaller. The adjustment is relative to the parameter value, e.g. 0.1 is 10%. The adjustment is rounded up to the
* next valid integer to ensure a new parameter value is created.
*
* @param value
* @param delta
* @param defaultRange
* The default range to apply the delta to in the case where the value is zero and no relative adjustment
* is possible.
* @return
*/
protected int updateParameter(int value, double delta, int defaultRange)
{
final int update;
if (value != 0)
update = (int) Math.ceil(value * Math.abs(delta));
else
update = (int) Math.ceil(defaultRange * Math.abs(delta));
if (delta < 0)
return value - update;
return value + update;
}
protected void setMin(double[] parameters, int index, double value)
{
if (parameters[index] > value)
parameters[index] = value;
}
protected void setMax(double[] parameters, int index, double value)
{
if (parameters[index] < value)
parameters[index] = value;
}
/**
* Create a new filter with the specified parameters
*
* @param parameters
* @return A new filter
*/
public abstract Filter create(double... parameters);
/**
* Creates a new filter with only the specified parameters enabled.
*
* @param enable
* the enabled flags
* @return the filter
*/
public Filter create(boolean[] enable)
{
if (enable == null || enable.length != getNumberOfParameters())
throw new IllegalArgumentException(
"Enable array must match the number of parameters: " + getNumberOfParameters());
final double[] p = new double[enable.length];
for (int i = 0; i < p.length; i++)
p[i] = (enable[i]) ? getParameterValueInternal(i) : getDisabledParameterValue(i);
return create(p);
}
/**
* Update the input array if the Filter's parameters are weaker. This method can be used to find the weakest
* parameters across a set of filters of the same type. The weakest filter can then be used to create a subset of
* pre-filtered results to use for testing the filter set.
*
* @param parameters
* The parameters
*/
public abstract void weakestParameters(double[] parameters);
/**
* Compare the two values and return a sort result for the minimum of the two
*
* @param value1
* the value 1
* @param value2
* the value 2
* @return the result (-1 is value1 is lower, 0 is equal, 1 is value2 is lower)
*/
public static int compareMin(double value1, double value2)
{
if (value1 < value2)
return -1;
if (value1 > value2)
return 1;
return 0;
}
/**
* Compare the two values and return a sort result for the maximum of the two
*
* @param value1
* the value 1
* @param value2
* the value 2
* @return the result (-1 is value1 is higher, 0 is equal, 1 is value2 is higher)
*/
public static int compareMax(double value1, double value2)
{
if (value1 < value2)
return 1;
if (value1 > value2)
return -1;
return 0;
}
/**
* Some filters requires all the data in a subset for scoring analysis. Others can create a subset using the fail
* count parameter for a smaller subset that will evaluate faster. This method returns true if the subset can be
* created using the fail count parameter that will be used to score the subset.
*
* @return True if the {@link #filterSubset(MemoryPeakResults, int, double[])} is valid
*/
public boolean subsetWithFailCount()
{
return true;
}
/*
* (non-Javadoc)
*
* @see gdsc.smlm.ga.Chromosome#length()
*/
public int length()
{
// Assume all the parameters are included in the Chromosome
return getNumberOfParameters();
}
/*
* (non-Javadoc)
*
* @see gdsc.smlm.ga.Chromosome#sequence()
*/
public double[] sequence()
{
// Assume all the parameters are included in the Chromosome
return getParameters();
}
/*
* (non-Javadoc)
*
* @see gdsc.smlm.ga.Chromosome#newChromosome(double[])
*/
public Chromosome<FilterScore> newChromosome(double[] sequence)
{
return create(sequence);
}
/*
* (non-Javadoc)
*
* @see gdsc.smlm.ga.Chromosome#lowerLimit()
*/
public double[] lowerLimit()
{
// Set zero as the lower limit
return new double[length()];
}
/*
* (non-Javadoc)
*
* @see gdsc.smlm.ga.Chromosome#upperLimit()
*/
public double[] upperLimit()
{
// No need for upper limits on filters
return null;
}
/*
* (non-Javadoc)
*
* @see gdsc.smlm.ga.Chromosome#setFitness(double)
*/
public void setFitness(FilterScore fitness)
{
this.fitness = fitness;
}
/*
* (non-Javadoc)
*
* @see gdsc.smlm.ga.Chromosome#getFitness()
*/
public FilterScore getFitness()
{
return fitness;
}
/**
* Return the Manhattan (city-block) distance between two chromosomes. This measure is intended to return if the
* sequences are the same (zero distance) or not). It is not intended for use in distance analysis.
*
* @see gdsc.smlm.ga.Chromosome#distance(gdsc.smlm.ga.Chromosome)
*/
public double distance(Chromosome<FilterScore> other)
{
// NOTE: If the distance is required for a certain type of analysis then this could be done
// using injection of an interface for calculating the distance.
final int n = FastMath.min(length(), other.length());
double[] s1 = sequence();
double[] s2 = other.sequence();
double d = 0;
for (int i = 0; i < n; i++)
d += Math.abs(s1[i] - s2[i]);
return d;
}
/*
* (non-Javadoc)
*
* @see gdsc.smlm.ga.Chromosome#equals(gdsc.smlm.ga.Chromosome)
*/
public boolean equals(Chromosome<FilterScore> other)
{
if (length() != other.length())
return false;
final int n = length();
double[] s1 = sequence();
double[] s2 = other.sequence();
for (int i = 0; i < n; i++)
if (s1[i] != s2[i])
return false;
return true;
}
/**
* Get the indices of the parameters that are included in the Chromosome interface. This can be used to look up the
* name of the parameter using {@link #getParameterName(int)}.
*
* @return The indices of the parameters that are included in the Chromosome interface
*/
public int[] getChromosomeParameters()
{
// Assume all the parameters are included in the Chromosome
return Utils.newArray(getNumberOfParameters(), 0, 1);
}
/**
* Return the value or Float.POSITIVE_INFINITY if value is not positive
*
* @param value
* @return The limit
*/
public static float getUpperLimit(double value)
{
if (value > 0)
return (float) value;
else
return Float.POSITIVE_INFINITY;
}
/**
* Return the value squared or Float.POSITIVE_INFINITY if value is not positive
*
* @param value
* @return The squared limit
*/
public static float getUpperSquaredLimit(double value)
{
if (value > 0)
return (float) (value * value);
else
return Float.POSITIVE_INFINITY;
}
/**
* Return the value or Double.POSITIVE_INFINITY if value is not positive
*
* @param value
* @return The limit
*/
public static double getDUpperLimit(double value)
{
if (value > 0)
return value;
else
return Double.POSITIVE_INFINITY;
}
/**
* Return the value squared or Double.POSITIVE_INFINITY if value is not positive
*
* @param value
* @return The squared limit
*/
public static double getDUpperSquaredLimit(double value)
{
if (value > 0)
return value * value;
else
return Double.POSITIVE_INFINITY;
}
/**
* @return The filter type
*/
public FilterType getFilterType()
{
return FilterType.STANDARD;
}
@Override
public Filter clone()
{
try
{
Filter f = (Filter) super.clone();
f.initialiseState();
return f;
}
catch (CloneNotSupportedException e)
{
return null;
}
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj)
{
if (obj == null)
{
return false;
}
if (obj == this)
{
return true;
}
if (!(obj instanceof Filter))
{
return false;
}
final Filter other = (Filter) obj;
final int size = getNumberOfParameters();
if (size != other.getNumberOfParameters())
{
return false;
}
// Check the types are the same before a parameter comparison
if (!this.getType().equals(other.getType()))
{
return false;
}
for (int i = 0; i < size; i++)
{
final double d1 = getParameterValueInternal(i);
final double d2 = other.getParameterValueInternal(i);
if (d1 != d2)
return false;
}
return true;
}
}