package gdsc.smlm.filters; import java.util.ArrayList; import java.util.List; import org.apache.commons.math3.util.FastMath; import gdsc.core.filters.NonMaximumSuppression; /*----------------------------------------------------------------------------- * 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. *---------------------------------------------------------------------------*/ /** * Identifies candidate spots (local maxima) in an image using non-maximum suppression. */ public abstract class MaximaSpotFilter extends SpotFilter { private final int search; private final int border; private NonMaximumSuppression nms; private DataProcessor scoreDataProcessor = null; private float[] data2 = null; /** * Create the spot filter * * @param search * The search width for non-maximum suppression * @param border * The border to ignore for maxima * @throws IllegalArgumentException * if search is below 1 or border is below zero */ public MaximaSpotFilter(int search, int border) { if (search < 1) throw new IllegalArgumentException("Search width must be 1 or above"); if (border < 0) throw new IllegalArgumentException("Border must be 0 or above"); this.search = search; this.border = border; nms = new NonMaximumSuppression(); // Do a neighbour check when using a low block size nms.setNeighbourCheck(search < 3); } /* * (non-Javadoc) * * @see gdsc.smlm.filters.SpotFilter#find(float[], int, int) */ protected Spot[] find(final float[] data, final int width, final int height) { data2 = preprocessData(data, width, height); //gdsc.core.ij.Utils.display("Spot Filter", new FloatProcessor(width, height, data2)); final int[] maxIndices = getMaxima(data2, width, height); if (maxIndices.length == 0) return null; // Use a data processor to generate a ranking score final float[] scoreData; if (scoreDataProcessor != null) { scoreData = data.clone(); scoreDataProcessor.process(scoreData, width, height); } else { scoreData = data2; } final Spot[] spots = new Spot[maxIndices.length]; for (int n = 0; n < maxIndices.length; n++) { final int y = maxIndices[n] / width; final int x = maxIndices[n] % width; final float intensity = data2[maxIndices[n]]; final float score = scoreData[maxIndices[n]]; spots[n] = new Spot(x, y, intensity, score); } return spots; } /* * (non-Javadoc) * * @see gdsc.smlm.filters.SpotFilter#getPreprocessedData() */ @Override public float[] getPreprocessedData() { return data2; } /** * Pre-process the data before finding local maxima * * @param data * @param width * @param height * @return The pre-processed data */ public abstract float[] preprocessData(final float[] data, final int width, final int height); /** * Find the indices of the maxima using the currently configured parameters * <p> * Data must be arranged in yx block order, i.e. height rows of width. * * @param data * @param width * @param height * @return Indices of the maxima */ protected int[] getMaxima(float[] data, int width, int height) { // Check upper limits are safe final int n = FastMath.min(search, FastMath.min(width, height)); final int border = FastMath.min(this.border, FastMath.min(width, height) / 2); return nms.blockFindInternal(data, width, height, n, border); } /** * @return the search width for maxima (maximum must be the highest point in a 2n+1 region) */ public int getSearch() { return search; } /** * @return the border at the edge to ignore for maxima */ public int getBorder() { return border; } @Override public List<String> getParameters() { ArrayList<String> list = new ArrayList<String>(); list.add("search = " + search); list.add("border = " + border); if (scoreDataProcessor != null) list.add("score = " + scoreDataProcessor.getDescription()); return list; } /* * (non-Javadoc) * * @see java.lang.Object#clone() */ public MaximaSpotFilter clone() { MaximaSpotFilter f = (MaximaSpotFilter) super.clone(); // Ensure the object is duplicated and not passed by reference. f.nms = nms.clone(); f.data2 = null; if (scoreDataProcessor != null) f.scoreDataProcessor = scoreDataProcessor.clone(); return f; } /** * @return the score data processor */ public DataProcessor getScoreDataProcessor() { return scoreDataProcessor; } /** * Set a data processor to use to generate a score for each spot that is used to rank them. If null then the score * is the same as the intensity score of the spot. * <p> * This allows the spots to be ranked independently of the intensity estimate that will be used for fitting. * * @param scoreDataProcessor * the score data processor */ public void setScoreDataProcessor(DataProcessor scoreDataProcessor) { this.scoreDataProcessor = scoreDataProcessor; } }