use of uk.ac.sussex.gdsc.smlm.search.SearchSpace in project GDSC-SMLM by aherbert.
the class BenchmarkFilterAnalysis method filterAnalysis.
private int filterAnalysis(FilterSet filterSet, int setNumber, DirectFilter currentOptimum, double rangeReduction) {
// Check if the filters are the same so allowing optimisation
final boolean allSameType = filterSet.allSameType();
this.gaResultsList = fitResultData.resultsList;
Chromosome<FilterScore> best = null;
String algorithm = "";
// All the search algorithms use search dimensions.
// Create search dimensions if needed (these are used for testing if the optimum is at the
// limit).
searchScoreFilter = null;
strengthLower = null;
strengthUpper = null;
FixedDimension[] originalDimensions = null;
boolean rangeInput = false;
boolean[] disabled = null;
double[][] seed = null;
// This flag is set if the analysis is not interactive. This occurs when running after the
// first iteration of an iterative analysis.
boolean nonInteractive = false;
if (allSameType) {
// There should always be 1 filter
searchScoreFilter = (DirectFilter) filterSet.getFilters().get(0);
final int n = searchScoreFilter.getNumberOfParameters();
// Option to configure a range
rangeInput = filterSet.getName().contains("Range");
final double[] range = new double[n];
if (rangeInput && filterSet.size() == 4) {
originalDimensions = new FixedDimension[n];
// This is used as min/lower/upper/max
final Filter minF = searchScoreFilter;
final Filter lowerF = filterSet.getFilters().get(1);
final Filter upperF = filterSet.getFilters().get(2);
final Filter maxF = filterSet.getFilters().get(3);
for (int i = 0; i < n; i++) {
final double min = minF.getParameterValue(i);
final double lower = lowerF.getParameterValue(i);
final double upper = upperF.getParameterValue(i);
range[i] = upper - lower;
final double max = maxF.getParameterValue(i);
final double minIncrement = searchScoreFilter.getParameterIncrement(i);
try {
originalDimensions[i] = new FixedDimension(min, max, minIncrement, lower, upper);
} catch (final IllegalArgumentException ex) {
ImageJUtils.log(TITLE + " : Unable to configure dimension [%d] %s: " + ex.getMessage(), i, searchScoreFilter.getParameterName(i));
originalDimensions = null;
rangeInput = false;
break;
}
}
}
if (rangeInput && (filterSet.size() == 3 || filterSet.size() == 2)) {
originalDimensions = new FixedDimension[n];
// This is used as lower/upper[/increment]
final Filter lowerF = searchScoreFilter;
final Filter upperF = filterSet.getFilters().get(1);
for (int i = 0; i < n; i++) {
// Do not disable if the increment is not set. This is left to the user to decide
// which parameters to optimise with the enabled checkboxes in the dialog.
final double lower = lowerF.getParameterValue(i);
final double upper = upperF.getParameterValue(i);
range[i] = upper - lower;
final ParameterType type = searchScoreFilter.getParameterType(i);
final double min = BenchmarkSpotFit.getMin(type);
final double max = BenchmarkSpotFit.getMax(type);
final double minIncrement = searchScoreFilter.getParameterIncrement(i);
try {
originalDimensions[i] = new FixedDimension(min, max, minIncrement, lower, upper);
} catch (final IllegalArgumentException ex) {
ImageJUtils.log(TITLE + " : Unable to configure dimension [%d] %s: " + ex.getMessage(), i, searchScoreFilter.getParameterName(i));
originalDimensions = null;
rangeInput = false;
break;
}
}
}
// Get the dimensions from the filters
if (originalDimensions == null) {
originalDimensions = new FixedDimension[n];
// Allow inputing a filter set (e.g. saved from previous optimisation)
// Find the limits in the current scores
final double[] lower = searchScoreFilter.getParameters().clone();
final double[] upper = lower.clone();
// Allow the SearchSpace algorithms to be seeded with an initial population
// for the first evaluation of the optimum. This is done when the input filter
// set is not a range.
seed = new double[filterSet.size()][];
int count = 0;
for (final Filter f : filterSet.getFilters()) {
final double[] point = f.getParameters();
seed[count++] = point;
for (int j = 0; j < lower.length; j++) {
if (lower[j] > point[j]) {
lower[j] = point[j];
}
if (upper[j] < point[j]) {
upper[j] = point[j];
}
}
}
// Get the search dimensions from the data.
// Min/max must be set using values from BenchmarkSpotFit.
boolean hasRange = false;
for (int i = 0; i < n; i++) {
if (lower[i] == upper[i]) {
// Not enabled
originalDimensions[i] = new FixedDimension(lower[i]);
continue;
}
hasRange = true;
final ParameterType type = searchScoreFilter.getParameterType(i);
double min = BenchmarkSpotFit.getMin(type);
double max = BenchmarkSpotFit.getMax(type);
final double minIncrement = searchScoreFilter.getParameterIncrement(i);
if (min > lower[i]) {
min = lower[i];
}
if (max < upper[i]) {
max = upper[i];
}
try {
originalDimensions[i] = new FixedDimension(min, max, minIncrement, lower[i], upper[i]);
} catch (final IllegalArgumentException ex) {
ImageJUtils.log(TITLE + " : Unable to configure dimension [%d] %s: " + ex.getMessage(), i, searchScoreFilter.getParameterName(i));
originalDimensions = null;
break;
}
}
if (!hasRange || originalDimensions == null) {
// Failed to work out the dimensions. No optimisation will be possible.
originalDimensions = null;
// Sort so that the filters are in a nice order for reporting
filterSet.sort();
// This will not be used when the dimensions are null
seed = null;
}
}
if (originalDimensions != null) {
// Use the current optimum if we are doing a range optimisation
if (currentOptimum != null && rangeInput && currentOptimum.getType().equals(searchScoreFilter.getType()) && settings.evolve != 0) {
// Suppress dialogs and use the current settings
nonInteractive = true;
final double[] p = currentOptimum.getParameters();
// Range search uses SearchDimension and we must centre on the optimum after creation.
for (int i = 0; i < originalDimensions.length; i++) {
final double centre = p[i];
double rangeFactor = 0;
if (originalDimensions[i].isActive()) {
// Set the range around the centre.
// This uses the range for each param when we read the filters.
rangeFactor = range[i];
// Optionally reduce the width of the dimensions.
if (rangeReduction > 0 && rangeReduction < 1) {
rangeFactor *= rangeReduction;
}
}
final double lower = centre - rangeFactor * 0.5;
final double upper = centre + rangeFactor * 0.5;
originalDimensions[i] = originalDimensions[i].create(lower, upper);
}
}
// Store the dimensions so we can do an 'at limit' check
disabled = new boolean[originalDimensions.length];
strengthLower = new double[originalDimensions.length];
strengthUpper = new double[originalDimensions.length];
for (int i = 0; i < disabled.length; i++) {
disabled[i] = !originalDimensions[i].isActive();
strengthLower[i] = originalDimensions[i].lower;
strengthUpper[i] = originalDimensions[i].upper;
}
}
} else {
// Sort so that the filters are in a nice order for reporting
filterSet.sort();
}
analysisStopWatch = StopWatch.createStarted();
if (settings.evolve == 1 && originalDimensions != null) {
// Collect parameters for the genetic algorithm
pauseFilterTimer();
// Remember the step size settings
double[] stepSize = stepSizeMap.get(setNumber);
if (stepSize == null || stepSize.length != searchScoreFilter.length()) {
stepSize = searchScoreFilter.mutationStepRange().clone();
for (int j = 0; j < stepSize.length; j++) {
stepSize[j] *= settings.delta;
}
// See if the same number of parameters have been optimised in other algorithms
final boolean[] enabled = searchRangeMap.get(setNumber);
if (enabled != null && enabled.length == stepSize.length) {
for (int j = 0; j < stepSize.length; j++) {
if (!enabled[j]) {
stepSize[j] *= -1;
}
}
}
}
GenericDialog gd = null;
final int[] indices = searchScoreFilter.getChromosomeParameters();
boolean runAlgorithm = nonInteractive;
if (!nonInteractive) {
// Ask the user for the mutation step parameters.
gd = new GenericDialog(TITLE);
final String prefix = setNumber + "_";
gd.addMessage("Configure the genetic algorithm for [" + setNumber + "] " + filterSet.getName());
gd.addNumericField(prefix + "Population_size", settings.populationSize, 0);
gd.addNumericField(prefix + "Failure_limit", settings.failureLimit, 0);
gd.addNumericField(prefix + "Tolerance", settings.tolerance, -1);
gd.addNumericField(prefix + "Converged_count", settings.convergedCount, 0);
gd.addSlider(prefix + "Mutation_rate", 0.05, 1, settings.mutationRate);
gd.addSlider(prefix + "Crossover_rate", 0.05, 1, settings.crossoverRate);
gd.addSlider(prefix + "Mean_children", 0.05, 3, settings.meanChildren);
gd.addSlider(prefix + "Selection_fraction", 0.05, 0.5, settings.selectionFraction);
gd.addCheckbox(prefix + "Ramped_selection", settings.rampedSelection);
gd.addCheckbox(prefix + "Save_option", settings.saveOption);
gd.addMessage("Configure the step size for each parameter");
for (int j = 0; j < indices.length; j++) {
// Do not mutate parameters that were not expanded, i.e. the input did not vary them.
final double step = (originalDimensions[indices[j]].isActive()) ? stepSize[j] : 0;
gd.addNumericField(getDialogName(prefix, searchScoreFilter, indices[j]), step, 2);
}
gd.showDialog();
runAlgorithm = !gd.wasCanceled();
}
if (runAlgorithm) {
// Used to create random sample
final FixedDimension[] dimensions = Arrays.copyOf(originalDimensions, originalDimensions.length);
if (!nonInteractive) {
if (gd == null) {
throw new IllegalStateException("The dialog has not been shown");
}
settings.populationSize = (int) Math.abs(gd.getNextNumber());
if (settings.populationSize < 10) {
settings.populationSize = 10;
}
settings.failureLimit = (int) Math.abs(gd.getNextNumber());
settings.tolerance = gd.getNextNumber();
// Allow negatives
settings.convergedCount = (int) gd.getNextNumber();
settings.mutationRate = Math.abs(gd.getNextNumber());
settings.crossoverRate = Math.abs(gd.getNextNumber());
settings.meanChildren = Math.abs(gd.getNextNumber());
settings.selectionFraction = Math.abs(gd.getNextNumber());
settings.rampedSelection = gd.getNextBoolean();
settings.saveOption = gd.getNextBoolean();
for (int j = 0; j < indices.length; j++) {
stepSize[j] = gd.getNextNumber();
}
// Store for repeat analysis
stepSizeMap.put(setNumber, stepSize);
}
if (disabled == null) {
disabled = new boolean[originalDimensions.length];
}
for (int j = 0; j < indices.length; j++) {
// A zero step size will keep the parameter but prevent range mutation.
if (stepSize[j] < 0) {
dimensions[indices[j]] = new FixedDimension(searchScoreFilter.getDisabledParameterValue(indices[j]));
disabled[indices[j]] = true;
}
}
// Create the genetic algorithm
final UniformRandomProvider rng = UniformRandomProviders.create();
final SimpleMutator<FilterScore> mutator = new SimpleMutator<>(rng, settings.mutationRate);
// Override the settings with the step length, a min of zero and the configured upper
final double[] upper = searchScoreFilter.upperLimit();
mutator.overrideChromosomeSettings(stepSize, new double[stepSize.length], upper);
final Recombiner<FilterScore> recombiner = new SimpleRecombiner<>(rng, settings.crossoverRate, settings.meanChildren);
SelectionStrategy<FilterScore> selectionStrategy;
// If the initial population is huge ensure that the first selection culls to the correct
// size
final int selectionMax = (int) (settings.selectionFraction * settings.populationSize);
if (settings.rampedSelection) {
selectionStrategy = new RampedSelectionStrategy<>(rng, settings.selectionFraction, selectionMax);
} else {
selectionStrategy = new SimpleSelectionStrategy<>(rng, settings.selectionFraction, selectionMax);
}
final ToleranceChecker<FilterScore> gaChecker = new InterruptChecker(settings.tolerance, settings.tolerance * 1e-3, settings.convergedCount);
// Create new random filters if the population is initially below the population size
List<Filter> filters = filterSet.getFilters();
if (filterSet.size() < settings.populationSize) {
filters = new ArrayList<>(settings.populationSize);
// Add the existing filters if they are not a range input file
if (!rangeInput) {
filters.addAll(filterSet.getFilters());
}
// Add current optimum to seed
if (currentOptimum != null && nonInteractive) {
filters.add(currentOptimum);
}
// The GA does not use the min interval grid so sample without rounding
final double[][] sample = SearchSpace.sampleWithoutRounding(dimensions, settings.populationSize - filters.size(), null);
filters.addAll(searchSpaceToFilters(sample));
}
gaPopulation = new Population<>(filters);
gaPopulation.setPopulationSize(settings.populationSize);
gaPopulation.setFailureLimit(settings.failureLimit);
selectionStrategy.setTracker(this);
// Evolve
algorithm = Settings.EVOLVE_OPTIONS[settings.evolve];
gaStatusPrefix = algorithm + " [" + setNumber + "] " + filterSet.getName() + " ... ";
gaIteration = 0;
gaPopulation.setTracker(this);
createGaWindow();
resumeFilterTimer();
best = gaPopulation.evolve(mutator, recombiner, this, selectionStrategy, gaChecker);
// In case optimisation was stopped
IJ.resetEscape();
if (best != null) {
// The GA may produce coordinates off the min interval grid
best = enumerateMinInterval(best, stepSize, indices);
// Now update the filter set for final assessment
filterSet = new FilterSet(filterSet.getName(), populationToFilters(gaPopulation.getIndividuals()));
// Option to save the filters
if (settings.saveOption) {
saveFilterSet(filterSet, setNumber, !nonInteractive);
}
}
} else {
resumeFilterTimer();
}
}
if ((settings.evolve == 2 || settings.evolve == 4) && originalDimensions != null) {
// Collect parameters for the range search algorithm
pauseFilterTimer();
final boolean isStepSearch = settings.evolve == 4;
// The step search should use a multi-dimension refinement and no range reduction
SearchSpace.RefinementMode myRefinementMode = SearchSpace.RefinementMode.MULTI_DIMENSION;
// Remember the enabled settings
boolean[] enabled = searchRangeMap.get(setNumber);
final int n = searchScoreFilter.getNumberOfParameters();
if (enabled == null || enabled.length != n) {
enabled = new boolean[n];
Arrays.fill(enabled, true);
// See if the same number of parameters have been optimised in other algorithms
final double[] stepSize = stepSizeMap.get(setNumber);
if (stepSize != null && enabled.length == stepSize.length) {
for (int j = 0; j < stepSize.length; j++) {
if (stepSize[j] < 0) {
enabled[j] = false;
}
}
}
}
GenericDialog gd = null;
boolean runAlgorithm = nonInteractive;
if (!nonInteractive) {
// Ask the user for the search parameters.
gd = new GenericDialog(TITLE);
final String prefix = setNumber + "_";
gd.addMessage("Configure the " + Settings.EVOLVE_OPTIONS[settings.evolve] + " algorithm for [" + setNumber + "] " + filterSet.getName());
gd.addSlider(prefix + "Width", 1, 5, settings.rangeSearchWidth);
if (!isStepSearch) {
gd.addCheckbox(prefix + "Save_option", settings.saveOption);
gd.addNumericField(prefix + "Max_iterations", settings.maxIterations, 0);
final String[] modes = SettingsManager.getNames((Object[]) SearchSpace.RefinementMode.values());
gd.addSlider(prefix + "Reduce", 0.01, 0.99, settings.rangeSearchReduce);
gd.addChoice("Refinement", modes, modes[settings.refinementMode]);
}
gd.addNumericField(prefix + "Seed_size", settings.seedSize, 0);
// Add choice of fields to optimise
for (int i = 0; i < n; i++) {
gd.addCheckbox(getDialogName(prefix, searchScoreFilter, i), enabled[i]);
}
gd.showDialog();
runAlgorithm = !gd.wasCanceled();
}
if (runAlgorithm) {
final SearchDimension[] dimensions = new SearchDimension[n];
if (!nonInteractive) {
if (gd == null) {
throw new IllegalStateException("The dialog has not been shown");
}
settings.rangeSearchWidth = (int) gd.getNextNumber();
if (!isStepSearch) {
settings.saveOption = gd.getNextBoolean();
settings.maxIterations = (int) gd.getNextNumber();
settings.rangeSearchReduce = gd.getNextNumber();
settings.refinementMode = gd.getNextChoiceIndex();
}
settings.seedSize = (int) gd.getNextNumber();
for (int i = 0; i < n; i++) {
enabled[i] = gd.getNextBoolean();
}
searchRangeMap.put(setNumber, enabled);
}
if (!isStepSearch) {
myRefinementMode = SearchSpace.RefinementMode.values()[settings.refinementMode];
}
for (int i = 0; i < n; i++) {
if (enabled[i]) {
try {
dimensions[i] = originalDimensions[i].create(settings.rangeSearchWidth);
dimensions[i].setPad(true);
// Prevent range reduction so that the step search just does a single refinement step
dimensions[i].setReduceFactor((isStepSearch) ? 1 : settings.rangeSearchReduce);
// Centre on current optimum
if (nonInteractive && currentOptimum != null) {
dimensions[i].setCentre(currentOptimum.getParameterValue(i));
}
} catch (final IllegalArgumentException ex) {
IJ.error(TITLE, String.format("Unable to configure dimension [%d] %s: " + ex.getMessage(), i, searchScoreFilter.getParameterName(i)));
return -1;
}
} else {
dimensions[i] = new SearchDimension(searchScoreFilter.getDisabledParameterValue(i));
}
}
if (disabled == null) {
disabled = new boolean[originalDimensions.length];
}
for (int i = 0; i < disabled.length; i++) {
disabled[i] = !dimensions[i].active;
}
// Check the number of combinations is OK
long combinations = SearchSpace.countCombinations(dimensions);
if (!nonInteractive && combinations > 10000) {
gd = new GenericDialog(TITLE);
ImageJUtils.addMessage(gd, "%d combinations for the configured dimensions.\n \nClick 'Yes' to optimise.", combinations);
gd.enableYesNoCancel();
gd.hideCancelButton();
gd.showDialog();
if (!gd.wasOKed()) {
combinations = 0;
}
}
if (combinations == 0) {
resumeFilterTimer();
} else {
algorithm = Settings.EVOLVE_OPTIONS[settings.evolve] + " " + settings.rangeSearchWidth;
gaStatusPrefix = algorithm + " [" + setNumber + "] " + filterSet.getName() + " ... ";
gaIteration = 0;
filterScoreOptimum = null;
final SearchSpace ss = new SearchSpace();
ss.setTracker(this);
if (settings.seedSize > 0) {
double[][] sample;
// Add current optimum to seed
if (nonInteractive && currentOptimum != null) {
sample = new double[1][];
sample[0] = currentOptimum.getParameters();
seed = merge(seed, sample);
}
final int size = (seed == null) ? 0 : seed.length;
// Sample without rounding as the seed will be rounded
sample = SearchSpace.sampleWithoutRounding(dimensions, settings.seedSize - size, null);
seed = merge(seed, sample);
}
// Note: If we have an optimum and we are not seeding this should not matter as the
// dimensions have been centred on the current optimum
ss.seed(seed);
final ConvergenceChecker<FilterScore> checker = new InterruptConvergenceChecker(0, 0, settings.maxIterations);
createGaWindow();
resumeFilterTimer();
final SearchResult<FilterScore> optimum = ss.search(dimensions, this, checker, myRefinementMode);
// In case optimisation was stopped
IJ.resetEscape();
if (optimum != null) {
best = ((SimpleFilterScore) optimum.getScore()).result.filter;
// Now update the filter set for final assessment
filterSet = new FilterSet(filterSet.getName(), searchSpaceToFilters((DirectFilter) best, ss.getSearchSpace()));
// Option to save the filters
if (settings.saveOption) {
saveFilterSet(filterSet, setNumber, !nonInteractive);
}
}
}
} else {
resumeFilterTimer();
}
}
if (settings.evolve == 3 && originalDimensions != null) {
// Collect parameters for the enrichment search algorithm
pauseFilterTimer();
boolean[] enabled = searchRangeMap.get(setNumber);
final int n = searchScoreFilter.getNumberOfParameters();
if (enabled == null || enabled.length != n) {
enabled = new boolean[n];
Arrays.fill(enabled, true);
// See if the same number of parameters have been optimised in other algorithms
final double[] stepSize = stepSizeMap.get(setNumber);
if (stepSize != null && enabled.length == stepSize.length) {
for (int j = 0; j < stepSize.length; j++) {
if (stepSize[j] < 0) {
enabled[j] = false;
}
}
}
}
GenericDialog gd = null;
boolean runAlgorithm = nonInteractive;
if (!nonInteractive) {
// Ask the user for the search parameters.
gd = new GenericDialog(TITLE);
final String prefix = setNumber + "_";
gd.addMessage("Configure the enrichment search algorithm for [" + setNumber + "] " + filterSet.getName());
gd.addCheckbox(prefix + "Save_option", settings.saveOption);
gd.addNumericField(prefix + "Max_iterations", settings.maxIterations, 0);
gd.addNumericField(prefix + "Converged_count", settings.convergedCount, 0);
gd.addNumericField(prefix + "Samples", settings.enrichmentSamples, 0);
gd.addSlider(prefix + "Fraction", 0.01, 0.99, settings.enrichmentFraction);
gd.addSlider(prefix + "Padding", 0, 0.99, settings.enrichmentPadding);
// Add choice of fields to optimise
for (int i = 0; i < n; i++) {
gd.addCheckbox(getDialogName(prefix, searchScoreFilter, i), enabled[i]);
}
gd.showDialog();
runAlgorithm = !gd.wasCanceled();
}
if (runAlgorithm) {
final FixedDimension[] dimensions = Arrays.copyOf(originalDimensions, originalDimensions.length);
if (!nonInteractive && gd != null) {
settings.saveOption = gd.getNextBoolean();
settings.maxIterations = (int) gd.getNextNumber();
settings.convergedCount = (int) gd.getNextNumber();
settings.enrichmentSamples = (int) gd.getNextNumber();
settings.enrichmentFraction = gd.getNextNumber();
settings.enrichmentPadding = gd.getNextNumber();
for (int i = 0; i < n; i++) {
enabled[i] = gd.getNextBoolean();
}
searchRangeMap.put(setNumber, enabled);
}
for (int i = 0; i < n; i++) {
if (!enabled[i]) {
dimensions[i] = new FixedDimension(searchScoreFilter.getDisabledParameterValue(i));
}
}
if (disabled == null) {
disabled = new boolean[originalDimensions.length];
}
for (int i = 0; i < disabled.length; i++) {
disabled[i] = !dimensions[i].active;
}
algorithm = Settings.EVOLVE_OPTIONS[settings.evolve];
gaStatusPrefix = algorithm + " [" + setNumber + "] " + filterSet.getName() + " ... ";
gaIteration = 0;
filterScoreOptimum = null;
final SearchSpace ss = new SearchSpace();
ss.setTracker(this);
// Add current optimum to seed
if (nonInteractive && currentOptimum != null) {
final double[][] sample = new double[1][];
sample[0] = currentOptimum.getParameters();
seed = merge(seed, sample);
}
ss.seed(seed);
final ConvergenceChecker<FilterScore> checker = new InterruptConvergenceChecker(0, 0, settings.maxIterations, settings.convergedCount);
createGaWindow();
resumeFilterTimer();
final SearchResult<FilterScore> optimum = ss.enrichmentSearch(dimensions, this, checker, settings.enrichmentSamples, settings.enrichmentFraction, settings.enrichmentPadding);
// In case optimisation was stopped
IJ.resetEscape();
if (optimum != null) {
best = ((SimpleFilterScore) optimum.getScore()).result.filter;
// Now update the filter set for final assessment
filterSet = new FilterSet(filterSet.getName(), searchSpaceToFilters((DirectFilter) best, ss.getSearchSpace()));
// Option to save the filters
if (settings.saveOption) {
saveFilterSet(filterSet, setNumber, !nonInteractive);
}
}
} else {
resumeFilterTimer();
}
}
IJ.showStatus("Analysing [" + setNumber + "] " + filterSet.getName() + " ...");
// Do not support plotting if we used optimisation
final double[] xValues = (best != null || isHeadless || (settings.plotTopN == 0)) ? null : new double[filterSet.size()];
final double[] yValues = (xValues != null) ? new double[xValues.length] : null;
SimpleFilterScore max = null;
// It can just assess the top 1 required for the summary.
if (best != null) {
// Only assess the top 1 filter for the summary
final List<Filter> list = new ArrayList<>();
list.add((DirectFilter) best);
filterSet = new FilterSet(filterSet.getName(), list);
}
// Score the filters and report the results if configured.
final FilterScoreResult[] scoreResults = scoreFilters(setUncomputedStrength(filterSet), settings.showResultsTable);
if (scoreResults == null) {
return -1;
}
analysisStopWatch.stop();
for (int index = 0; index < scoreResults.length; index++) {
final FilterScoreResult scoreResult = scoreResults[index];
if (xValues != null && yValues != null) {
xValues[index] = scoreResult.filter.getNumericalValue();
yValues[index] = scoreResult.score;
}
final SimpleFilterScore result = new SimpleFilterScore(scoreResult, allSameType, scoreResult.criteria >= minCriteria);
if (result.compareTo(max) < 0) {
max = result;
}
}
if (max == null) {
return -1;
}
if (settings.showResultsTable) {
addToResultsWindow(scoreResults);
}
// Check the top filter against the limits of the original dimensions
char[] atLimit = null;
if (allSameType && originalDimensions != null) {
if (disabled == null) {
disabled = new boolean[originalDimensions.length];
}
final DirectFilter filter = max.result.filter;
final int[] indices = filter.getChromosomeParameters();
atLimit = new char[indices.length];
final StringBuilder sb = new StringBuilder(200);
for (int j = 0; j < indices.length; j++) {
atLimit[j] = ComplexFilterScore.WITHIN;
final int p = indices[j];
if (disabled[p]) {
continue;
}
final double value = filter.getParameterValue(p);
final double lowerLimit = originalDimensions[p].getLower();
final double upperLimit = originalDimensions[p].getUpper();
final int c1 = Double.compare(value, lowerLimit);
if (c1 <= 0) {
atLimit[j] = ComplexFilterScore.FLOOR;
sb.append(" : ").append(filter.getParameterName(p)).append(' ').append(atLimit[j]).append('[').append(MathUtils.rounded(value));
if (c1 == -1) {
atLimit[j] = ComplexFilterScore.BELOW;
sb.append("<").append(MathUtils.rounded(lowerLimit));
}
sb.append("]");
} else {
final int c2 = Double.compare(value, upperLimit);
if (c2 >= 0) {
atLimit[j] = ComplexFilterScore.CEIL;
sb.append(" : ").append(filter.getParameterName(p)).append(' ').append(atLimit[j]).append('[').append(MathUtils.rounded(value));
if (c2 == 1) {
atLimit[j] = ComplexFilterScore.ABOVE;
sb.append(">").append(MathUtils.rounded(upperLimit));
}
sb.append("]");
}
}
}
if (sb.length() > 0) {
if (max.criteriaPassed) {
ImageJUtils.log("Warning: Top filter (%s @ %s|%s) [%s] at the limit of the expanded range%s", filter.getName(), MathUtils.rounded((invertScore) ? -max.score : max.score), MathUtils.rounded((invertCriteria) ? -minCriteria : minCriteria), limitFailCount + fitResultData.limitRange, sb.toString());
} else {
ImageJUtils.log("Warning: Top filter (%s @ -|%s) [%s] at the limit of the expanded range%s", filter.getName(), MathUtils.rounded((invertCriteria) ? -max.criteria : max.criteria), limitFailCount + fitResultData.limitRange, sb.toString());
}
}
}
// Note that max should never be null since this method is not run with an empty filter set
// We may have no filters that pass the criteria
final String type = max.result.filter.getType();
if (!max.criteriaPassed) {
ImageJUtils.log("Warning: Filter does not pass the criteria: %s : Best = %s using %s", type, MathUtils.rounded((invertCriteria) ? -max.criteria : max.criteria), max.result.filter.getName());
return 0;
}
// This could be an option?
final boolean allowDuplicates = true;
// No requirement to be the same type to store for later analysis.
// All filter sets should be able to have a best
// filter irrespective of whether they were the same type or not.
final ComplexFilterScore newFilterScore = new ComplexFilterScore(max.result, atLimit, algorithm, analysisStopWatch.getTime(), "", 0);
addBestFilter(type, allowDuplicates, newFilterScore);
// Add spacer at end of each result set
if (isHeadless) {
if (settings.showResultsTable && filterSet.size() > 1) {
IJ.log("");
}
} else {
if (settings.showResultsTable && filterSet.size() > 1) {
resultsWindow.append("");
}
if (settings.plotTopN > 0 && xValues != null) {
// 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();
if (unique) {
// Check the values all refer to the same property
for (final 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.0;
}
}
final String title = filterSet.getName();
// Check if a previous filter set had the same name, update if necessary
final NamedPlot plot = getNamedPlot(title);
if (plot == null) {
filterAnalysisResult.plots.add(new NamedPlot(title, xAxisName, xValues, yValues));
} else {
plot.updateValues(xAxisName, xValues, yValues);
}
if (filterAnalysisResult.plots.size() > settings.plotTopN) {
Collections.sort(filterAnalysisResult.plots, NamedPlot::compare);
filterAnalysisResult.plots.remove(filterAnalysisResult.plots.size() - 1);
}
}
}
return 0;
}
use of uk.ac.sussex.gdsc.smlm.search.SearchSpace in project GDSC-SMLM by aherbert.
the class BenchmarkFilterAnalysis method parameterAnalysis.
/**
* Run the optimum filter on a set of labelled peak results using various parameter settings
* outputting performance statistics on the success of the filter to an ImageJ table.
*
* <p>If a new optimum is found the class level static parameters are updated.
*
* @param nonInteractive True if non interactive
* @param currentOptimum the optimum
* @param rangeReduction the range reduction
* @return the best filter
*/
private ComplexFilterScore parameterAnalysis(boolean nonInteractive, ComplexFilterScore currentOptimum, double rangeReduction) {
this.gaResultsList = fitResultData.resultsList;
String algorithm = "";
// All the search algorithms use search dimensions.
searchScoreFilter = currentOptimum.result.filter;
final FixedDimension[] originalDimensions = new FixedDimension[3];
double[] point = createParameters();
final String[] names = { "Fail count", "Residuals threshold", "Duplicate distance" };
// Start at -1 so an exception constructing the dimension can use the same index
// to log the error.
int index = -1;
try {
originalDimensions[++index] = new FixedDimension(settings.minFailCount, settings.maxFailCount, 1);
// TODO - let the min intervals be configured, maybe via extra options
if (computeDoublets) {
originalDimensions[++index] = new FixedDimension(settings.minResidualsThreshold, settings.maxResidualsThreshold, 0.05);
} else {
originalDimensions[++index] = new FixedDimension(1, 1, 0.05);
}
originalDimensions[++index] = new FixedDimension(settings.minDuplicateDistance, settings.maxDuplicateDistance, 0.5);
} catch (final IllegalArgumentException ex) {
ImageJUtils.log(TITLE + " : Unable to configure dimension [%d] %s: " + ex.getMessage(), index, names[index]);
return null;
}
// Check for a search
boolean active = false;
for (int i = 0; i < originalDimensions.length; i++) {
if (originalDimensions[i].isActive()) {
active = true;
break;
}
}
if (!active) {
ImageJUtils.log(TITLE + " : No search range");
return currentOptimum;
}
// Optionally use a reduced range (this is used for iteration)
if (rangeReduction > 0 && rangeReduction < 1) {
// Suppress dialogs and use the current settings
nonInteractive = true;
for (int i = 0; i < originalDimensions.length; i++) {
final double centre = point[i];
double rangeFactor = 0;
if (originalDimensions[i].isActive()) {
rangeFactor = (originalDimensions[i].max - originalDimensions[i].min) * rangeReduction;
}
final double lower = centre - rangeFactor * 0.5;
final double upper = centre + rangeFactor * 0.5;
originalDimensions[i] = originalDimensions[i].create(lower, upper);
}
}
analysisStopWatch = StopWatch.createStarted();
// Store this for later debugging
SearchResult<FilterScore> optimum = null;
if (settings.searchParam == 0 || settings.searchParam == 2) {
// Collect parameters for the range search algorithm
pauseParameterTimer();
final boolean isStepSearch = settings.searchParam == 2;
// The step search should use a multi-dimension refinement and no range reduction
SearchSpace.RefinementMode myRefinementMode = SearchSpace.RefinementMode.MULTI_DIMENSION;
GenericDialog gd = null;
boolean runAlgorithm = nonInteractive;
if (!nonInteractive) {
// Ask the user for the search parameters.
gd = new GenericDialog(TITLE);
gd.addMessage("Configure the " + Settings.SEARCH_OPTIONS[settings.searchParam] + " algorithm for " + searchScoreFilter.getType());
gd.addSlider("Width", 1, 5, settings.paRangeSearchWidth);
if (!isStepSearch) {
gd.addNumericField("Max_iterations", settings.paMaxIterations, 0);
final String[] modes = SettingsManager.getNames((Object[]) SearchSpace.RefinementMode.values());
gd.addSlider("Reduce", 0.01, 0.99, settings.paRangeSearchReduce);
gd.addChoice("Refinement", modes, modes[settings.paRefinementMode]);
}
gd.addNumericField("Seed_size", settings.paSeedSize, 0);
gd.showDialog();
runAlgorithm = !gd.wasCanceled();
}
if (runAlgorithm) {
final SearchDimension[] dimensions = new SearchDimension[originalDimensions.length];
if (!nonInteractive && gd != null) {
settings.paRangeSearchWidth = (int) gd.getNextNumber();
if (!isStepSearch) {
settings.paMaxIterations = (int) gd.getNextNumber();
settings.paRangeSearchReduce = gd.getNextNumber();
settings.paRefinementMode = gd.getNextChoiceIndex();
}
settings.paSeedSize = (int) gd.getNextNumber();
}
if (!isStepSearch) {
myRefinementMode = SearchSpace.RefinementMode.values()[settings.paRefinementMode];
}
for (int i = 0; i < dimensions.length; i++) {
if (originalDimensions[i].isActive()) {
try {
dimensions[i] = originalDimensions[i].create(settings.paRangeSearchWidth);
dimensions[i].setPad(true);
// Prevent range reduction so that the step search just does a single refinement step
dimensions[i].setReduceFactor((isStepSearch) ? 1 : settings.paRangeSearchReduce);
// Centre on current optimum
dimensions[i].setCentre(point[i]);
} catch (final IllegalArgumentException ex) {
IJ.error(TITLE, String.format("Unable to configure dimension [%d] %s: %s", i, names[i], ex.getMessage()));
return null;
}
} else {
dimensions[i] = new SearchDimension(point[i]);
}
}
// Check the number of combinations is OK
long combinations = SearchSpace.countCombinations(dimensions);
if (!nonInteractive && combinations > 2000) {
gd = new GenericDialog(TITLE);
ImageJUtils.addMessage(gd, "%d combinations for the configured dimensions.\n \nClick 'Yes' to optimise.", combinations);
gd.enableYesNoCancel();
gd.hideCancelButton();
gd.showDialog();
if (!gd.wasOKed()) {
combinations = 0;
}
}
if (combinations == 0) {
resumeParameterTimer();
} else {
algorithm = Settings.SEARCH_OPTIONS[settings.searchParam] + " " + settings.paRangeSearchWidth;
gaStatusPrefix = algorithm + " " + searchScoreFilter.getName() + " ... ";
gaIteration = 0;
parameterScoreOptimum = null;
final SearchSpace ss = new SearchSpace();
ss.setTracker(this);
if (settings.paSeedSize > 0) {
// Add current optimum to seed
// Note: If we have an optimum and we are not seeding this should not matter as the
// dimensions have been centred on the current optimum
final double[][] seed = new double[1][];
seed[0] = point;
// Sample without rounding as the seed will be rounded
final double[][] sample = SearchSpace.sampleWithoutRounding(dimensions, settings.paSeedSize - 1, null);
ss.seed(merge(seed, sample));
}
final ConvergenceChecker<FilterScore> checker = new InterruptConvergenceChecker(0, 0, settings.paMaxIterations);
createGaWindow();
resumeParameterTimer();
optimum = ss.search(dimensions, new ParameterScoreFunction(), checker, myRefinementMode);
// In case optimisation was stopped
IJ.resetEscape();
if (optimum != null) {
// Now update the parameters for final assessment
point = optimum.getPoint();
}
}
} else {
resumeParameterTimer();
}
}
if (settings.searchParam == 1) {
// Collect parameters for the enrichment search algorithm
pauseParameterTimer();
GenericDialog gd = null;
boolean runAlgorithm = nonInteractive;
if (!nonInteractive) {
// Ask the user for the search parameters.
gd = new GenericDialog(TITLE);
gd.addMessage("Configure the " + Settings.SEARCH_OPTIONS[settings.searchParam] + " algorithm for " + searchScoreFilter.getType());
gd.addNumericField("Max_iterations", settings.paMaxIterations, 0);
gd.addNumericField("Converged_count", settings.paConvergedCount, 0);
gd.addNumericField("Samples", settings.paEnrichmentSamples, 0);
gd.addSlider("Fraction", 0.01, 0.99, settings.paEnrichmentFraction);
gd.addSlider("Padding", 0, 0.99, settings.paEnrichmentPadding);
gd.showDialog();
runAlgorithm = !gd.wasCanceled();
}
if (runAlgorithm) {
final FixedDimension[] dimensions = Arrays.copyOf(originalDimensions, originalDimensions.length);
if (!nonInteractive && gd != null) {
settings.paMaxIterations = (int) gd.getNextNumber();
settings.paConvergedCount = (int) gd.getNextNumber();
settings.paEnrichmentSamples = (int) gd.getNextNumber();
settings.paEnrichmentFraction = gd.getNextNumber();
settings.paEnrichmentPadding = gd.getNextNumber();
}
algorithm = Settings.SEARCH_OPTIONS[settings.searchParam];
gaStatusPrefix = algorithm + " " + searchScoreFilter.getName() + " ... ";
gaIteration = 0;
parameterScoreOptimum = null;
final SearchSpace ss = new SearchSpace();
ss.setTracker(this);
// Add current optimum to seed
final double[][] seed = new double[1][];
seed[0] = point;
ss.seed(seed);
final ConvergenceChecker<FilterScore> checker = new InterruptConvergenceChecker(0, 0, settings.paMaxIterations, settings.paConvergedCount);
createGaWindow();
resumeParameterTimer();
optimum = ss.enrichmentSearch(dimensions, new ParameterScoreFunction(), checker, settings.paEnrichmentSamples, settings.paEnrichmentFraction, settings.paEnrichmentPadding);
// In case optimisation was stopped
IJ.resetEscape();
if (optimum != null) {
point = optimum.getPoint();
}
} else {
resumeParameterTimer();
}
}
if (settings.searchParam == 3) {
// Collect parameters for the enumeration search algorithm
pauseParameterTimer();
final SearchDimension[] dimensions = new SearchDimension[originalDimensions.length];
for (int i = 0; i < dimensions.length; i++) {
if (originalDimensions[i].isActive()) {
try {
dimensions[i] = originalDimensions[i].create(0);
} catch (final IllegalArgumentException ex) {
IJ.error(TITLE, String.format("Unable to configure dimension [%d] %s: %s", i, names[i], ex.getMessage()));
return null;
}
} else {
dimensions[i] = new SearchDimension(point[i]);
}
}
GenericDialog gd = null;
long combinations = SearchSpace.countCombinations(dimensions);
if (!nonInteractive && combinations > 2000) {
gd = new GenericDialog(TITLE);
ImageJUtils.addMessage(gd, "%d combinations for the configured dimensions.\n \nClick 'Yes' to optimise.", combinations);
gd.enableYesNoCancel();
gd.hideCancelButton();
gd.showDialog();
if (!gd.wasOKed()) {
combinations = 0;
}
}
if (combinations == 0) {
resumeParameterTimer();
} else {
algorithm = Settings.SEARCH_OPTIONS[settings.searchParam];
gaStatusPrefix = algorithm + " " + searchScoreFilter.getName() + " ... ";
gaIteration = 0;
parameterScoreOptimum = null;
final SearchSpace ss = new SearchSpace();
ss.setTracker(this);
createGaWindow();
resumeParameterTimer();
optimum = ss.findOptimum(dimensions, new ParameterScoreFunction());
// In case optimisation was stopped
IJ.resetEscape();
if (optimum != null) {
// Now update the parameters for final assessment
point = optimum.getPoint();
}
}
}
IJ.showStatus("Analysing " + searchScoreFilter.getName() + " ...");
// Update the parameters using the optimum
settings.failCount = (int) Math.round(point[0]);
residualsThreshold = settings.residualsThreshold = point[1];
settings.duplicateDistance = point[2];
// Refresh the coordinate store
if (coordinateStore == null || // Due to the scaling factor the distance may not be exactly the same
DoubleEquality.relativeError(settings.duplicateDistance, coordinateStore.getXyResolution() / distanceScallingFactor) > 0.01) {
coordinateStore = createCoordinateStore();
}
createResultsPrefix2();
// (Re) Score the filter.
// TODO - check this is now OK. Maybe remove the enumeration on the min interval grid
// If scoring of filter here is different to scoring in the optimisation routine it is probably
// an ss_filter.clone() issue,
// i.e. multi-threading use of the filter clone is not working.
// Or it could be that the optimisation produced params off the min-interval grid
final FilterScoreResult scoreResult = scoreFilter(searchScoreFilter);
if (optimum != null && (scoreResult.score != optimum.getScore().score && scoreResult.criteria != optimum.getScore().criteria)) {
final ParameterScoreResult r = scoreFilter((DirectFilter) searchScoreFilter.clone(), defaultMinimalFilter, settings.failCount, residualsThreshold, settings.duplicateDistance, createCoordinateStore(settings.duplicateDistance), false);
ImageJUtils.log("Weird re-score of the filter: %f!=%f or %f!=%f (%f:%f)", scoreResult.score, optimum.getScore().score, scoreResult.criteria, optimum.getScore().criteria, r.score, r.criteria);
}
final SimpleFilterScore max = new SimpleFilterScore(scoreResult, true, scoreResult.criteria >= minCriteria);
analysisStopWatch.stop();
if (settings.showResultsTable) {
addToResultsWindow(scoreResult.text);
}
// Check the top result against the limits of the original dimensions
final StringBuilder sb = new StringBuilder(200);
for (int j = 0; j < originalDimensions.length; j++) {
if (!originalDimensions[j].isActive()) {
continue;
}
final double value = point[j];
final double lowerLimit = originalDimensions[j].getLower();
final double upperLimit = originalDimensions[j].getUpper();
final int c1 = Double.compare(value, lowerLimit);
if (c1 <= 0) {
sb.append(" : ").append(names[j]).append(' ').append(ComplexFilterScore.FLOOR).append('[').append(MathUtils.rounded(value));
if (c1 == -1) {
sb.append("<").append(MathUtils.rounded(lowerLimit));
}
sb.append("]");
} else {
final int c2 = Double.compare(value, upperLimit);
if (c2 >= 0) {
sb.append(" : ").append(names[j]).append(' ').append(ComplexFilterScore.CEIL).append('[').append(MathUtils.rounded(value));
if (c2 == 1) {
sb.append(">").append(MathUtils.rounded(upperLimit));
}
sb.append("]");
}
}
}
if (sb.length() > 0) {
if (max.criteriaPassed) {
ImageJUtils.log("Warning: Top filter (%s @ %s|%s) [%s] at the limit of the expanded range%s", searchScoreFilter.getName(), MathUtils.rounded((invertScore) ? -max.score : max.score), MathUtils.rounded((invertCriteria) ? -minCriteria : minCriteria), limitFailCount + fitResultData.limitRange, sb.toString());
} else {
ImageJUtils.log("Warning: Top filter (%s @ -|%s) [%s] at the limit of the expanded range%s", searchScoreFilter.getName(), MathUtils.rounded((invertCriteria) ? -max.criteria : max.criteria), limitFailCount + fitResultData.limitRange, sb.toString());
}
}
// We may have no filters that pass the criteria
final String type = max.result.filter.getType();
if (!max.criteriaPassed) {
ImageJUtils.log("Warning: Filter does not pass the criteria: %s : Best = %s using %s", type, MathUtils.rounded((invertCriteria) ? -max.criteria : max.criteria), max.result.filter.getName());
return null;
}
// Update without duplicates
final boolean allowDuplicates = false;
// Re-use the atLimit and algorithm for the input optimum
final ComplexFilterScore newFilterScore = new ComplexFilterScore(max.result, currentOptimum.atLimit, currentOptimum.algorithm, currentOptimum.time, algorithm, analysisStopWatch.getTime());
addBestFilter(type, allowDuplicates, newFilterScore);
// Add spacer at end of each result set
if (settings.showResultsTable) {
addToResultsWindow("");
}
if (newFilterScore.compareTo(currentOptimum) <= 0) {
return newFilterScore;
}
// Update the algorithm and time
currentOptimum.paramAlgorithm = algorithm;
currentOptimum.paramTime = analysisStopWatch.getTime();
return currentOptimum;
}
use of uk.ac.sussex.gdsc.smlm.search.SearchSpace in project GDSC-SMLM by aherbert.
the class BenchmarkFilterAnalysis method enumerateMinInterval.
/**
* Enumerate on the min interval to convert an off grid result to one on the grid.
*
* @param best The optimum
* @param enabled Array specifying which parameters are enabled
* @param indices Array specifying which parameter indices to search
* @return The optimum on the min interval grid
*/
private Chromosome<FilterScore> enumerateMinInterval(Chromosome<FilterScore> best, boolean[] enabled, int[] indices) {
// Enumerate on the min interval to produce the final filter
searchScoreFilter = (DirectFilter) best;
filterScoreOptimum = null;
SearchDimension[] dimensions2 = new SearchDimension[searchScoreFilter.getNumberOfParameters()];
for (int i = 0; i < indices.length; i++) {
final int j = indices[i];
if (enabled[j]) {
final double minIncrement = searchScoreFilter.getParameterIncrement(j);
try {
final double value = searchScoreFilter.getParameterValue(j);
final double max = MathUtils.ceil(value, minIncrement);
final double min = MathUtils.floor(value, minIncrement);
dimensions2[i] = new SearchDimension(min, max, minIncrement, 1);
dimensions2[i].setCentre(value);
dimensions2[i].setIncrement(minIncrement);
} catch (final IllegalArgumentException ex) {
IJ.error(TITLE, String.format("Unable to configure dimension [%d] %s: %s", j, searchScoreFilter.getParameterName(j), ex.getMessage()));
dimensions2 = null;
break;
}
}
}
if (dimensions2 != null) {
// Add dimensions that have been missed
for (int i = 0; i < dimensions2.length; i++) {
if (dimensions2[i] == null) {
dimensions2[i] = new SearchDimension(searchScoreFilter.getParameterValue(i));
}
}
final SearchSpace ss = new SearchSpace();
ss.setTracker(this);
final SearchResult<FilterScore> optimum = ss.findOptimum(dimensions2, this);
if (optimum != null) {
best = ((SimpleFilterScore) optimum.getScore()).result.filter;
}
}
return best;
}
Aggregations