package gdsc.smlm.engine;
import gdsc.core.utils.Maths;
import gdsc.smlm.results.PeakResult;
/**
* Stores a set of results within a grid arrangement at a given resolution. Allows listing neighbours of a given
* position.
*/
public class ResultGridManager
{
private class PeakList
{
int size = 0;
PeakResult[] list = null;
void add(PeakResult peak)
{
if (list == null)
list = new PeakResult[4];
else if (list.length == size)
{
final PeakResult[] list2 = new PeakResult[size * 2];
System.arraycopy(list, 0, list2, 0, size);
list = list2;
}
list[size++] = peak;
}
}
private CandidateList[][] candidateGrid;
private PeakList[][] peakGrid;
private final int resolution, xBlocks, yBlocks;
private PeakResult[] peakCache = null;
private int peakCacheX = -1, peakCacheY = -1;
private CandidateList neighbourCache = null;
private Candidate neighbourCacheCandidate = null;
/**
* Clear the cache. This should be called when more data has been added to the grid.
*/
public void clearCache()
{
peakCache = null;
peakCacheX = -1;
peakCacheY = -1;
neighbourCache = null;
neighbourCacheCandidate = null;
}
/**
* Create a grid for candidates or peak results
*
* @param maxx
* @param maxy
* @param resolution
*/
public ResultGridManager(int maxx, int maxy, int resolution)
{
this.resolution = resolution;
xBlocks = getBlock(maxx) + 1;
yBlocks = getBlock(maxy) + 1;
createCandidateGrid();
createPeakGrid();
}
private void createPeakGrid()
{
peakGrid = new PeakList[xBlocks][yBlocks];
for (int x = 0; x < xBlocks; x++)
{
final PeakList[] list = peakGrid[x];
for (int y = 0; y < yBlocks; y++)
{
list[y] = new PeakList();
}
}
}
private void createCandidateGrid()
{
candidateGrid = new CandidateList[xBlocks][yBlocks];
for (int x = 0; x < xBlocks; x++)
{
final CandidateList[] list = candidateGrid[x];
for (int y = 0; y < yBlocks; y++)
{
list[y] = new CandidateList();
}
}
}
/**
* Create a grid only of peak results. No candidates can be added to the grid.
*
* @param results
* @param resolution
*/
public ResultGridManager(PeakResult[] results, double resolution)
{
this.resolution = Maths.max(1, (int) Math.ceil(resolution));
double maxx = 0, maxy = 0;
for (PeakResult p : results)
{
if (maxx < p.getXPosition())
maxx = p.getXPosition();
if (maxy < p.getYPosition())
maxy = p.getYPosition();
}
xBlocks = getBlock((int) maxx) + 1;
yBlocks = getBlock((int) maxy) + 1;
createPeakGrid();
for (PeakResult p : results)
putOnGrid(p);
}
private int getBlock(final int x)
{
return x / resolution;
}
/**
* Add a peak to the grid. Assumes that the coordinates are within the size of the grid.
*
* @param peak
*/
public void addToGrid(PeakResult peak)
{
final int xBlock = getBlock((int) peak.getXPosition());
final int yBlock = getBlock((int) peak.getYPosition());
peakGrid[xBlock][yBlock].add(peak);
clearCache();
}
/**
* Add a peak to the grid. Assumes that the coordinates are within the size of the grid.
* <p>
* This method does not clear the cache and should be called only when initialising the grid.
*
* @param peak
* the peak
*/
public void putOnGrid(PeakResult peak)
{
final int xBlock = getBlock((int) peak.getXPosition());
final int yBlock = getBlock((int) peak.getYPosition());
peakGrid[xBlock][yBlock].add(peak);
}
/**
* Add a candidate to the grid. Assumes that the coordinates are within the size of the grid.
*
* @param candidate
*/
public void addToGrid(Candidate candidate)
{
final int xBlock = getBlock(candidate.x);
final int yBlock = getBlock(candidate.y);
candidateGrid[xBlock][yBlock].add(candidate);
clearCache();
}
/**
* Add a candidate to the grid. Assumes that the coordinates are within the size of the grid.
* <p>
* This method does not clear the cache and should be called only when initialising the grid.
*
* @param candidate
* the candidate
*/
public void putOnGrid(Candidate candidate)
{
final int xBlock = getBlock(candidate.x);
final int yBlock = getBlock(candidate.y);
candidateGrid[xBlock][yBlock].add(candidate);
}
/**
* Get the neighbours in the local region (defined by the input resolution). All neighbours within the
* resolution
* distance will be returned, plus there may be others and so distances should be checked.
*
* @param x
* @param y
* @return the neighbours
*/
public PeakResult[] getPeakResultNeighbours(final int x, final int y)
{
final int xBlock = getBlock(x);
final int yBlock = getBlock(y);
if (peakCache != null && peakCacheX == xBlock && peakCacheY == yBlock)
return peakCache;
int size = 0;
final int xmin = Math.max(0, xBlock - 1);
final int ymin = Math.max(0, yBlock - 1);
final int xmax = Math.min(xBlocks, xBlock + 2);
final int ymax = Math.min(yBlocks, yBlock + 2);
for (int xx = xmin; xx < xmax; xx++)
{
for (int yy = ymin; yy < ymax; yy++)
{
size += peakGrid[xx][yy].size;
}
}
final PeakResult[] list = new PeakResult[size];
if (size != 0)
{
size = 0;
for (int xx = xmin; xx < xmax; xx++)
{
for (int yy = ymin; yy < ymax; yy++)
{
if (peakGrid[xx][yy].size == 0)
continue;
System.arraycopy(peakGrid[xx][yy].list, 0, list, size, peakGrid[xx][yy].size);
size += peakGrid[xx][yy].size;
}
}
}
peakCache = list;
peakCacheX = xBlock;
peakCacheY = yBlock;
return list;
}
/**
* Get the neighbours in the local region (defined by the input resolution). All neighbours within the
* resolution distance will be returned, plus there may be others and so distances should be checked.
* <p>
* Note: Assumes candidate indices are unique.
*
* @param candidate
* the candidate
* @return the neighbours
*/
public CandidateList getNeighbours(final Candidate candidate)
{
return getNeighbours(candidate, false);
}
/**
* Get the neighbours in the local region (defined by the input resolution). All neighbours within the
* resolution distance will be returned, plus there may be others and so distances should be checked.
*
* @param candidate
* the candidate
* @param nonUnique
* True if candidate indices are not unique
* @return the neighbours
*/
public CandidateList getNeighbours(final Candidate candidate, boolean nonUnique)
{
if (neighbourCache != null && neighbourCacheCandidate != null &&
neighbourCacheCandidate.index == candidate.index)
return neighbourCache;
// Get all
final Candidate[] list = getCandidates(candidate.x, candidate.y);
// Remove the candidate.
int size;
if (nonUnique)
{
// Assumes non-unique candidates
size = 0;
for (int i = 0; i < list.length; i++)
{
if (list[i].index == candidate.index)
continue;
list[size++] = list[i];
}
}
else
{
size = list.length;
for (int i = 0; i < size; i++)
{
if (list[i].index == candidate.index)
{
int remaining = list.length - i - 1;
if (remaining != 0)
System.arraycopy(list, i + 1, list, i, remaining);
size--;
// Assume a unique candidate index
break;
}
}
}
neighbourCache = new CandidateList(size, list);
neighbourCacheCandidate = candidate;
return neighbourCache;
}
/**
* Get the neighbours in the local region (defined by the input resolution). All neighbours within the
* resolution
* distance will be returned, plus there may be others and so distances should be checked.
*
* @param x
* @param y
* @return the neighbours
*/
private Candidate[] getCandidates(final int x, final int y)
{
final int xBlock = getBlock(x);
final int yBlock = getBlock(y);
int size = 0;
final int xmin = Math.max(0, xBlock - 1);
final int ymin = Math.max(0, yBlock - 1);
final int xmax = Math.min(xBlocks, xBlock + 2);
final int ymax = Math.min(yBlocks, yBlock + 2);
for (int xx = xmin; xx < xmax; xx++)
{
for (int yy = ymin; yy < ymax; yy++)
{
size += candidateGrid[xx][yy].size;
}
}
final Candidate[] list = new Candidate[size];
if (size != 0)
{
size = 0;
for (int xx = xmin; xx < xmax; xx++)
{
for (int yy = ymin; yy < ymax; yy++)
{
if (candidateGrid[xx][yy].size == 0)
continue;
System.arraycopy(candidateGrid[xx][yy].list, 0, list, size, candidateGrid[xx][yy].size);
size += candidateGrid[xx][yy].size;
}
}
}
return list;
}
}