package gdsc.smlm.results;
import java.awt.Rectangle;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
import gdsc.smlm.function.gaussian.Gaussian2DFunction;
/*-----------------------------------------------------------------------------
* GDSC SMLM Software
*
* Copyright (C) 2013 Alex Herbert
* Genome Damage and Stability Centre
* University of Sussex, UK
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*---------------------------------------------------------------------------*/
/**
* Stores peak results in memory. The PeakResults interface add methods are thread safe as they are synchronized. There
* are equivalent non-.synchronized methods.
*/
public class MemoryPeakResults extends AbstractPeakResults implements Cloneable, Iterable<PeakResult>
{
private static LinkedHashMap<String, MemoryPeakResults> resultsMap = new LinkedHashMap<String, MemoryPeakResults>();
private static final Runtime s_runtime = Runtime.getRuntime();
private static int byteSize = 0;
private static int byteSizeWithDeviations = 0;
private static final int DEFAULT_SIZE = 96;
private static final int DEFAULT_SIZE_WITH_DEVIATIONS = 144;
private ArrayList<PeakResult> results;
private boolean sortAfterEnd;
public MemoryPeakResults()
{
results = new ArrayList<PeakResult>(1000);
}
public MemoryPeakResults(int capacity)
{
results = new ArrayList<PeakResult>(capacity);
}
/**
* @param name
* The name of the results
* @return Get the named results (or null if they do not exist)
*/
public static MemoryPeakResults getResults(String name)
{
return resultsMap.get(name);
}
/**
* @param name
* The name of the results
* @return The removed results (or null if they do not exist)
*/
public static MemoryPeakResults removeResults(String name)
{
return resultsMap.remove(name);
}
/**
* Add the results to memory. The name is taken from the results.
*
* @param results
*/
public static void addResults(MemoryPeakResults results)
{
if (results == null)
throw new NullPointerException("Results must not be null");
results.trimToSize();
resultsMap.put(results.getName(), results);
}
/**
* @return A set of the available named results held in memory
*/
public static Set<String> getResultNames()
{
return resultsMap.keySet();
}
/**
* @return A collection of the results held in memory
*/
public static Collection<MemoryPeakResults> getAllResults()
{
return resultsMap.values();
}
/**
* Count the number of result sets in memory
*/
public static int getResultsMemorySize()
{
return resultsMap.size();
}
/**
* Return true if there are no non-empty results in memory
*/
public static boolean isMemoryEmpty()
{
if (resultsMap.isEmpty())
return true;
for (MemoryPeakResults r : resultsMap.values())
if (!r.isEmpty())
return false;
return true;
}
/**
* Count the total number of results in memory
*/
public static int countMemorySize()
{
int size = 0;
for (MemoryPeakResults r : resultsMap.values())
{
size += r.size();
}
return size;
}
/**
* Estimate the total size of results in memory
*/
public static long estimateMemorySize()
{
long memorySize = 0;
for (MemoryPeakResults r : resultsMap.values())
{
memorySize += estimateMemorySize(r.getResults());
}
return memorySize;
}
/**
* Clear the results from memory
*/
public static void clearMemory()
{
resultsMap.clear();
}
/*
* (non-Javadoc)
*
* @see gdsc.utils.fitting.results.PeakResults#begin()
*/
public void begin()
{
// Q. Should a new array be allocated if the previous one was very large?
if (results.size() > 10000)
results = new ArrayList<PeakResult>(1000);
else
results.clear();
}
/**
* Add a result. Synchronized.
*
* {@inheritDoc}
*
* @see gdsc.utils.fitting.results.PeakResults#add(int, int, int, float, double, float, float[], float[])
*/
public synchronized void add(int peak, int origX, int origY, float origValue, double chiSquared, float noise,
float[] params, float[] paramsStdDev)
{
results.add(new PeakResult(peak, origX, origY, origValue, chiSquared, noise, params, paramsStdDev));
}
/**
* Add a result. Not synchronized.
*
* @param peak
* the peak (e.g. frame number)
* @param origX
* the original X position
* @param origY
* the original Y position
* @param origValue
* the original value
* @param error
* the error
* @param noise
* the noise
* @param params
* the parameters
* @param paramsStdDev
* the parameters standard deviation (or null)
*/
public void addf(int peak, int origX, int origY, float origValue, double error, float noise, float[] params,
float[] paramsStdDev)
{
results.add(new PeakResult(peak, origX, origY, origValue, error, noise, params, paramsStdDev));
}
/**
* Add all results. Synchronized.
*
* {@inheritDoc}
*
* @see gdsc.utils.fitting.results.PeakResults#addAll(java.util.Collection)
*/
public synchronized void addAll(Collection<PeakResult> results)
{
this.results.addAll(results);
}
/**
* Add all results. Not synchronized.
*
*/
public void addAllf(Collection<PeakResult> results)
{
this.results.addAll(results);
}
/**
* Add a result. Not synchronized.
*
* @param result
* the result
*/
@Override
public void add(PeakResult result)
{
results.add(result);
}
/**
* Add a result. Synchronized.
*
* @param result
*/
public synchronized void addSync(PeakResult result)
{
results.add(result);
}
/*
* (non-Javadoc)
*
* @see gdsc.utils.fitting.results.PeakResults#size()
*/
public int size()
{
return results.size();
}
/**
* @see {@link java.util.ArrayList#trimToSize() }
*/
public void trimToSize()
{
results.trimToSize();
}
/*
* (non-Javadoc)
*
* @see gdsc.utils.fitting.results.PeakResults#end()
*/
public void end()
{
if (isSortAfterEnd())
sort();
}
/**
* Sort the results
*/
public void sort()
{
Collections.sort(results);
}
/**
* @return The peak results
*/
public List<PeakResult> getResults()
{
return results;
}
/**
* Gets the bounds.
*
* @param calculate
* Set to true to calculate the bounds if they are null or zero width/height
* @return the bounds of the result coordinates
*/
public Rectangle getBounds(boolean calculate)
{
if ((bounds == null || bounds.width == 0 || bounds.height == 0) && calculate)
{
bounds = new Rectangle();
Rectangle2D.Float b = getDataBounds();
// Round to integer
bounds.x = (int) Math.floor(b.x);
bounds.y = (int) Math.floor(b.y);
int maxX = (int) Math.ceil(b.x + b.width);
int maxY = (int) Math.ceil(b.y + b.height);
// For compatibility with drawing images add one to the limits if they are integers
// Q. Is this still necessary since drawing images has been re-written to handle edge cases?
//if (maxX == b.x + b.width)
// maxX += 1;
//if (maxY == b.y + b.height)
// maxY += 1;
bounds.width = maxX - bounds.x;
bounds.height = maxY - bounds.y;
}
return bounds;
}
/**
* Gets the data bounds.
*
* @return the bounds of the result coordinates
*/
public Rectangle2D.Float getDataBounds()
{
if (isEmpty())
return new Rectangle2D.Float();
float minX = Float.POSITIVE_INFINITY, minY = Float.POSITIVE_INFINITY;
float maxX = Float.NEGATIVE_INFINITY, maxY = Float.NEGATIVE_INFINITY;
for (PeakResult result : results)
{
if (minX > result.params[Gaussian2DFunction.X_POSITION])
minX = result.params[Gaussian2DFunction.X_POSITION];
if (maxX < result.params[Gaussian2DFunction.X_POSITION])
maxX = result.params[Gaussian2DFunction.X_POSITION];
if (minY > result.params[Gaussian2DFunction.Y_POSITION])
minY = result.params[Gaussian2DFunction.Y_POSITION];
if (maxY < result.params[Gaussian2DFunction.Y_POSITION])
maxY = result.params[Gaussian2DFunction.Y_POSITION];
}
return new Rectangle2D.Float(minX, minY, maxX - minX, maxY - minY);
}
/**
* Returns a list iterator over the elements in this list (in proper
* sequence).
*
* <p>
* The returned list iterator is <a href="#fail-fast"><i>fail-fast</i></a>.
*
* @see java.util.List#listIterator()
*/
public ListIterator<PeakResult> listIterator()
{
return results.listIterator();
}
/**
* Returns an iterator over the elements in this list in proper sequence.
*
* <p>
* The returned iterator is <a href="#fail-fast"><i>fail-fast</i></a>.
*
* @see java.util.List#iterator()
* @return an iterator over the elements in this list in proper sequence
*/
public Iterator<PeakResult> iterator()
{
return results.iterator();
}
/*
* (non-Javadoc)
*
* @see gdsc.utils.fitting.results.PeakResults#isActive()
*/
public boolean isActive()
{
return true;
}
/**
* @param sortAfterEnd
* True if the results should be sorted after the {@link #end()} method
*/
public void setSortAfterEnd(boolean sortAfterEnd)
{
this.sortAfterEnd = sortAfterEnd;
}
/**
* @return True if the results should be sorted after the {@link #end()} method
*/
public boolean isSortAfterEnd()
{
return sortAfterEnd;
}
/**
* Convert the size in bytes into a string
*
* @param memorySize
* @return The memory size string
*/
public static String memorySizeString(long memorySize)
{
return memorySize < 10000 * 1024 ? memorySize / 1024L + "K" : memorySize / 1048576L + "MB";
}
/**
* Return an estimate of the memory size taken by PeakResult objects.
* <p>
* Note: This is just a guess based on measured sizes for the objects in memory.
*
* @param size
* @param includeDeviations
* @return The memory size
*/
public static long estimateMemorySize(List<PeakResult> results)
{
long memorySize = 0;
if (results != null && results.size() > 0)
{
boolean includeDeviations = results.get(0).paramsStdDev != null;
memorySize = MemoryPeakResults.estimateMemorySize(results.size(), includeDeviations);
}
return memorySize;
}
/**
* Return an estimate of the memory size taken by PeakResult objects.
* <p>
* Note: This is just a guess based on measured sizes for the objects in memory.
*
* @param size
* @param includeDeviations
* @return The memory size
*/
public static long estimateMemorySize(int size, boolean includeDeviations)
{
if (byteSize == 0)
{
// Comment out to speed up the code
//byteSize = (int) (measureSize(10000, false) / 10000);
//byteSizeWithDeviations = (int) (measureSize(10000, true) / 10000);
//System.out.printf("Size = %d, Size with deviations = %d", byteSize, byteSizeWithDeviations);
// Check just in case the estimate is bad
if (byteSize <= 0)
byteSize = DEFAULT_SIZE;
if (byteSizeWithDeviations <= 0)
byteSizeWithDeviations = DEFAULT_SIZE_WITH_DEVIATIONS;
}
return size * ((includeDeviations) ? byteSize : byteSizeWithDeviations);
}
// The following code can be used to determine the memory size of an object.
// Taken from: http://www.javaworld.com/javaworld/javatips/jw-javatip130.html?page=1
public static long measureSize(int size, boolean includeDeviations)
{
// Warm up all classes/methods we will use
runGC();
usedMemory();
// Array to keep strong references to allocated objects
final int count = 1000;
Object[] objects = new Object[count];
long heap1 = 0;
// Allocate count+1 objects, discard the first one
for (int i = -1; i < count; ++i)
{
Object object = null;
// Instantiate your data here and assign it to object
object = new PeakResult(0, 1, 2, 3.0f, 4.0, 5.0f, new float[7], (includeDeviations) ? new float[7] : null);
if (i >= 0)
objects[i] = object;
else
{
object = null; // Discard the warm up object
runGC();
heap1 = usedMemory(); // Take a before heap snapshot
}
}
runGC();
long heap2 = usedMemory(); // Take an after heap snapshot:
long memorySize = Math.round(((double) (heap2 - heap1)) / count);
//System.out.println("'before' heap: " + heap1 + ", 'after' heap: " + heap2);
//System.out.println("heap delta: " + (heap2 - heap1) + ", {" + objects[0].getClass() + "} size = " + memorySize +
// " bytes");
for (int i = 0; i < count; ++i)
objects[i] = null;
objects = null;
runGC();
return memorySize * size;
}
/**
* Run the garbage collector multiple times to free memory
*/
public static void runGC()
{
// It helps to call Runtime.gc()
// using several method calls:
for (int r = 0; r < 4; ++r)
_runGC();
}
private static void _runGC()
{
long usedMem1 = usedMemory(), usedMem2 = Long.MAX_VALUE;
for (int i = 0; (usedMem1 < usedMem2) && (i < 500); ++i)
{
runGCOnce();
Thread.currentThread();
Thread.yield();
usedMem2 = usedMem1;
usedMem1 = usedMemory();
}
}
public static void runGCOnce()
{
s_runtime.runFinalization();
s_runtime.gc();
}
public static long usedMemory()
{
return s_runtime.totalMemory() - s_runtime.freeMemory();
}
public static long totalMemory()
{
return s_runtime.totalMemory();
}
public static long freeMemory()
{
return s_runtime.freeMemory();
}
/**
* Shallow copy this set of results. To create new object references use {@link #copy()}.
*
* @see java.lang.Object#clone()
*/
@Override
public MemoryPeakResults clone()
{
try
{
return (MemoryPeakResults) super.clone();
}
catch (CloneNotSupportedException e)
{
// This should not happen so ignore
}
return null;
}
/**
* Copy the results. Create new objects for the properties (avoiding a shallow copy) but does not
* deep copy all of the peak results. Allows results to be resorted but not modified.
*/
public MemoryPeakResults copy()
{
MemoryPeakResults copy = clone();
if (copy != null)
{
// Deep copy the objects
if (bounds != null)
copy.bounds = new Rectangle(bounds);
if (calibration != null)
copy.calibration = calibration.clone();
if (results != null)
copy.results = new ArrayList<PeakResult>(results);
}
return copy;
}
/**
* @return True if empty
*/
public boolean isEmpty()
{
return results.isEmpty();
}
/**
* Convert to an array.
*
* @return the peak result array
*/
public PeakResult[] toArray()
{
return results.toArray(new PeakResult[size()]);
}
/**
* Convert to an array reusing the space if provided.
*
* @param array
* the array (can be null)
* @return the peak result array
*/
public PeakResult[] toArray(PeakResult[] array)
{
if (array == null)
return toArray();
return results.toArray(array);
}
/**
* Gets the head position in the set of results.
*
* @return the head
*/
public PeakResult getHead()
{
if (isEmpty())
return null;
return results.get(0);
}
/**
* Gets the tail position in the set of results.
*
* @return the tail
*/
public PeakResult getTail()
{
if (isEmpty())
return null;
return results.get(size() - 1);
}
/**
* Checks if all results have a stored precision value.
*
* @return true, if all results have a stored precision value
*/
public boolean hasStoredPrecision()
{
for (int i = 0; i < results.size(); i++)
{
if (!results.get(i).hasPrecision())
return false;
}
return true;
}
/**
* Checks for null results in the store.
*
* @return true, if null PeakResult object(s) exist
*/
public boolean hasNullResults()
{
for (int i = 0; i < results.size(); i++)
{
if (results.get(i) == null)
return true;
}
return false;
}
/**
* Removes the null results from the store.
*/
public void removeNullResults()
{
ArrayList<PeakResult> list = new ArrayList<PeakResult>(size());
// Use an iterator to take advantage of the fail-fast thread safety
for (PeakResult e : results)
{
if (e != null)
list.add(e);
}
this.results = list;
}
}