package gdsc.smlm.ij.results;
import gdsc.smlm.utils.XmlUtils;
import gdsc.core.ij.Utils;
import gdsc.smlm.function.gaussian.Gaussian2DFunction;
import gdsc.smlm.results.PeakResult;
import ij.ImagePlus;
import ij.ImageStack;
import ij.MappedImageStack;
import ij.WindowManager;
import ij.gui.Roi;
import ij.measure.Calibration;
import ij.plugin.LutLoader;
import ij.process.FloatProcessor;
import ij.process.ImageProcessor;
import ij.process.MappedFloatProcessor;
import ij.process.ShortProcessor;
import java.awt.Rectangle;
import java.util.Arrays;
import java.util.Collection;
/*-----------------------------------------------------------------------------
* 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.
*---------------------------------------------------------------------------*/
/**
* Saves the fit results to an ImageJ image
*/
public class IJImagePeakResults extends IJAbstractPeakResults
{
public static final String IMAGE_SUFFIX = "SuperRes";
/**
* Display the signal of the peak in the image. The default is a count of 1.
*/
public static final int DISPLAY_SIGNAL = 1;
/**
* Interpolate the value over multiple pixels. Depending on the location in the containing pixel this is usually the
* 3 closest 8-connected neighbours and the containing pixel. It may be less if the containing pixel is at the image
* bounds.
*/
public static final int DISPLAY_WEIGHTED = 2;
/**
* Equalise the histogram of the output image. Allows showing a high dynamic range by limiting bright pixels.
*/
public static final int DISPLAY_EQUALIZED = 4;
/**
* Display the peak number in the image. The default is a count of 1.
*/
public static final int DISPLAY_PEAK = 8;
/**
* Display the peak error in the image. The default is a count of 1.
*/
public static final int DISPLAY_ERROR = 16;
/**
* Replace the pixels with the new value. This should not be used with {@link #DISPLAY_WEIGHTED} to avoid the value
* being interpolated over multiple pixels.
*/
public static final int DISPLAY_REPLACE = 32;
/**
* Use the maximum value. This should not be used with {@link #DISPLAY_WEIGHTED} to avoid the value being
* interpolated over multiple pixels.
*/
public static final int DISPLAY_MAX = 64;
/**
* Use this to support negative values
*/
public static final int DISPLAY_NEGATIVES = 128;
/**
* Mapped all non-zero values to 1-255 in the 8-bit displayed image. Zero and below are mapped to 0 in the LUT.
* <p>
* This cannot be used with {@link #DISPLAY_EQUALIZED} or {@link #DISPLAY_NEGATIVES}.
*/
public static final int DISPLAY_MAPPED = 256;
/**
* Mapped even zero to 1-255 in the 8-bit displayed image. -0.0f and below is mapped to 0 in the LUT. This can be
* used for example to display the result of a probability calculation where 0 is a valid display value but must be
* distinguished from pixels that have no value computed.
* <p>
* Must be used with {@link #DISPLAY_MAPPED}.
*/
public static final int DISPLAY_MAP_ZERO = 512;
/** The empty value. */
private double EMPTY = 0.0;
protected final String title;
protected final int imageWidth;
protected final int imageHeight;
protected final float scale;
protected int size = 0;
protected double[] data;
protected final float xlimit;
protected final float ylimit;
protected ImagePlus imp = null;
protected boolean imageActive = false;
protected int displayFlags = 0;
private int rollingWindowSize = 0;
private boolean displayImage = true;
private boolean liveImage = true;
// Used to draw the image
private int lastPaintSize = 0;
private int nextRepaintSize = 0;
private long nextPaintTime = 0;
private Object pixels;
private boolean imageLock = false;
private double repaintInterval = 0.1;
private long repaintDelay = 1000;
private int currentFrame;
private String lutName = "fire";
/**
* @param title
* Title of the image (appended with a suffix)
* @param bounds
* Define the bounding rectangle of the image coordinates. Any results outside this will not be
* displayed.
* @param scale
* The image scale. Must be strictly positive.
*/
public IJImagePeakResults(String title, Rectangle bounds, float scale)
{
if (scale <= 0 || Float.isNaN(scale))
throw new IllegalArgumentException("Invalid scale: " + scale);
this.title = title + " " + IMAGE_SUFFIX;
this.bounds = (Rectangle) bounds.clone();
if (bounds.width < 0)
bounds.width = 0;
if (bounds.height < 0)
bounds.height = 0;
this.scale = scale;
imageWidth = ceil(bounds.width * scale);
imageHeight = ceil(bounds.height * scale);
// Set the limits used to check if a coordinate has 4 neighbour cells
xlimit = imageWidth - 1;
ylimit = imageHeight - 1;
}
private int ceil(float f)
{
return (int) Math.ceil(f);
}
/*
* (non-Javadoc)
*
* @see gdsc.utils.fitting.PeakResults#begin()
*/
public void begin()
{
imageActive = false;
preBegin();
// Handle invalid bounds with an empty single pixel image
boolean validBounds = imageWidth > 0 && imageHeight > 0 &&
(double) imageWidth * (double) imageHeight < Integer.MAX_VALUE;
int w, h;
if (validBounds)
{
w = imageWidth;
h = imageHeight;
}
else
{
Utils.log("ERROR: Unable to create image results '%s' due to invalid dimensions: width=%d, height=%d",
title, imageWidth, imageHeight);
w = h = 1;
}
size = 0;
lastPaintSize = 0;
nextRepaintSize = 20; // Let some results appear before drawing
nextPaintTime = System.currentTimeMillis() + repaintDelay;
imageLock = false;
data = new double[w * h];
// Use negative zero so that we know when positive zero has been written to the array.
if ((displayFlags & (DISPLAY_MAPPED | DISPLAY_MAP_ZERO)) == (DISPLAY_MAPPED | DISPLAY_MAP_ZERO))
EMPTY = -0.0f;
resetData();
imp = WindowManager.getImage(title);
currentFrame = 1;
ImageProcessor ip = createNewProcessor(w, h);
if (imp == null)
{
imp = new ImagePlus(title, ip);
// Apply the fire lookup table
WindowManager.setTempCurrentImage(imp);
LutLoader lut = new LutLoader();
lut.run(lutName);
WindowManager.setTempCurrentImage(null);
if (displayImage)
imp.show();
}
else
{
// Copy the lookup table
ip.setColorModel(imp.getProcessor().getColorModel());
ImageStack stack = createNewImageStack(w, h);
stack.addSlice(null, ip);
// If resizing then remove adornments
if (stack.getWidth() != imp.getWidth() || stack.getHeight() != imp.getHeight())
{
imp.setOverlay(null);
imp.setRoi((Roi) null);
}
imp.setStack(stack);
if (displayImage)
imp.show();
else
imp.hide();
}
imp.setProperty("Info", createInfo());
if (calibration != null)
{
Calibration cal = new Calibration();
String unit = "nm";
double unitPerPixel = calibration.getNmPerPixel() / scale;
if (unitPerPixel > 100)
{
unit = "um";
unitPerPixel /= 1000.0;
}
cal.setUnit(unit);
cal.pixelHeight = cal.pixelWidth = unitPerPixel;
imp.setCalibration(cal);
}
// We cannot draw anything with no bounds
imageActive = validBounds;
}
private String createInfo()
{
StringBuilder sb = new StringBuilder();
if (source != null)
sb.append("Source: ").append(source.toXML()).append("\n");
if (bounds != null)
sb.append("Bounds: ").append(getBoundsString()).append("\n");
if (calibration != null)
sb.append("Calibration:\n").append(XmlUtils.toXML(calibration)).append("\n");
if (configuration != null)
sb.append("Configuration:\n").append(configuration).append("\n");
return (sb.length() > 0) ? sb.toString() : null;
}
/**
* Check the display flags when {@link #begin()} is called to ensure the image settings are OK. Update the flags if
* necessary.
* <p>
* Use to perform any other processing before begin().
*/
protected void preBegin()
{
// Signal is OK to be equalised
if ((displayFlags & DISPLAY_SIGNAL) != 0)
{
}
// Peak and localisation should not use equalisation
else
{
displayFlags &= ~DISPLAY_EQUALIZED;
}
// Display peaks cannot use weighting and should show the exact frame number so use replace
if ((displayFlags & DISPLAY_PEAK) != 0)
{
displayFlags &= ~DISPLAY_WEIGHTED;
displayFlags |= DISPLAY_REPLACE;
}
// Mapped values (above zero) cannot use equalisation or be negative
if ((displayFlags & DISPLAY_MAPPED) != 0)
{
displayFlags &= ~DISPLAY_EQUALIZED;
displayFlags &= ~DISPLAY_NEGATIVES;
}
}
private ImageProcessor createNewProcessor(int imageWidth, int imageHeight)
{
// Equalised display requires a 16-bit image to allow fast processing of the histogram
if ((displayFlags & DISPLAY_EQUALIZED) != 0)
{
pixels = new short[data.length];
return new ShortProcessor(imageWidth, imageHeight, (short[]) pixels, null);
}
else
{
pixels = new float[data.length];
// Special float processor that maps all values to 1-255 in the LUT.
// Zero is mapped to 0 in the LUT.
if ((displayFlags & DISPLAY_MAPPED) != 0)
{
MappedFloatProcessor fp = new MappedFloatProcessor(imageWidth, imageHeight, (float[]) pixels, null);
fp.setMapZero((displayFlags & DISPLAY_MAP_ZERO) != 0);
return fp;
}
return new FloatProcessor(imageWidth, imageHeight, (float[]) pixels, null);
}
}
private ImageStack createNewImageStack(int w, int h)
{
if ((displayFlags & DISPLAY_MAPPED) != 0)
{
MappedImageStack stack = new MappedImageStack(w, h);
stack.setMapZero((displayFlags & DISPLAY_MAP_ZERO) != 0);
return stack;
}
return new ImageStack(w, h);
}
/**
* Create the image from a clone of the current data. Should only be called by one thread which has the lock so can
* use class variables and the actual pixel buffer.
*
* @return The size when the image data was cloned
*/
private void createImage()
{
double[] data;
synchronized (this.data)
{
data = this.data.clone();
lastPaintSize = this.size;
setNextRepaintSize(lastPaintSize);
if (repaintDelay != 0)
nextPaintTime = System.currentTimeMillis() + repaintDelay;
}
if ((displayFlags & DISPLAY_EQUALIZED) != 0)
{
// 16-bit image
// Get the current maximum
double max = data[0];
for (int i = 1; i < data.length; i++)
{
if (max < data[i])
max = data[i];
}
// Compress into 16-bit image if necessary
int K = 65535;
double norm = K / max;
short[] pixels = (short[]) this.pixels;
for (int i = 0; i < pixels.length; i++)
{
int index = (int) (norm * data[i]);
if (index > K)
index = K;
pixels[i] = (short) index;
}
// Get the histogram
int[] H = new int[K + 1];
for (int i = 0; i < pixels.length; i++)
H[pixels[i] & 0xffff]++;
// Skip empty data
int start = 1;
while (H[start] == 0 && start < K)
start++;
//System.out.printf("Start = %d\n", start);
// Perform weighted histogram equalisation
// See: ij.plugin.ContrastEnhancer
double[] sqrt = new double[H.length];
sqrt[start] = Math.sqrt(H[start]);
double sum = sqrt[start];
for (int i = start + 1; i < K; i++)
{
sqrt[i] = Math.sqrt(H[i]);
sum += 2 * sqrt[i];
}
sum += Math.sqrt(H[K]);
double scale = K / sum;
int[] lut = new int[K + 1];
lut[0] = 0;
sum = sqrt[start];
for (int i = start + 1; i < K; i++)
{
double delta = sqrt[i];
sum += delta;
lut[i] = (int) (sum * scale + 0.5);
sum += delta;
}
lut[K] = K;
for (int i = 0; i < pixels.length; i++)
pixels[i] = (short) lut[pixels[i] & 0xffff];
imp.setDisplayRange(0, K);
}
else
{
// 32-bit image. Just copy the data but find the maximum
float[] pixels = (float[]) this.pixels;
double max = data[0];
double min = 0;
for (int i = 0; i < data.length; i++)
{
if (max < data[i])
max = data[i];
pixels[i] = (float) data[i];
}
if ((displayFlags & DISPLAY_NEGATIVES) != 0)
{
for (float f : pixels)
if (min > f)
min = f;
}
imp.setDisplayRange(min, max);
}
}
/*
* (non-Javadoc)
*
* @see gdsc.smlm.results.AbstractPeakResults#add(int, int, int, float, double, float, float[], float[])
*/
public void add(int peak, int origX, int origY, float origValue, double error, float noise, float[] params,
float[] paramsDev)
{
if (!imageActive)
return;
final float x = mapX(params[Gaussian2DFunction.X_POSITION]);
final float y = mapY(params[Gaussian2DFunction.Y_POSITION]);
// Check bounds
if (x < 0 || x >= imageWidth || y < 0 || y >= imageHeight)
return;
checkAndUpdateToFrame(peak);
int[] indices = new int[5];
float[] values = new float[4];
getValue(peak, params, error, x, y, indices, values);
addData(1, indices[4], indices, values);
updateImage();
}
private void addData(int nPoints, int nValues, int[] indices, float[] values)
{
// Add the values to the configured indices
synchronized (data)
{
size += nPoints;
if ((displayFlags & DISPLAY_REPLACE) != 0)
{
// Replace the data
for (int i = nValues; i-- > 0;)
data[indices[i]] = values[i];
}
else if ((displayFlags & DISPLAY_MAX) != 0)
{
// Use the highest value
for (int i = nValues; i-- > 0;)
data[indices[i]] = max(data[indices[i]], values[i]);
}
else
{
// Add the data
for (int i = nValues; i-- > 0;)
data[indices[i]] += values[i];
}
}
}
private static double max(final double a, final double b)
{
// Ignore possible NaNs or infinity
return (a > b) ? a : b;
}
/**
* Map x to the location on the output image.
*
* @param x
* the x
* @return the output x
*/
public float mapX(float x)
{
return (x - bounds.x) * scale;
}
/**
* Map y to the location on the output image.
*
* @param y
* the y
* @return the output y
*/
public float mapY(float y)
{
return (y - bounds.y) * scale;
}
/**
* Gets the value to add to the image data.
* <p>
* We construct the indices based on the current settings. 1, 2, or 4 indices will be returned with their values.
* The number of indices is stored in the 5th position of the indices array.
*
* @param peak
* the peak
* @param params
* the peak params
* @param error
* the peak error
* @param x
* the x position
* @param y
* the y position
* @param indices
* the indices
* @param value
* the values for the indices
* @return the value
*/
private void getValue(int peak, float[] params, double error, float x, float y, int[] indices, float[] value)
{
final float v;
// Use the signal for the count
if ((displayFlags & DISPLAY_SIGNAL) != 0)
{
v = params[Gaussian2DFunction.SIGNAL];
}
// Use the peak number for the count
else if ((displayFlags & DISPLAY_PEAK) != 0)
{
v = peak;
}
// Use the peak number for the count
else if ((displayFlags & DISPLAY_ERROR) != 0)
{
v = (float) error;
}
else
{
v = 1;
}
getValue(v, x, y, indices, value);
}
/**
* Gets the value to add to the image data.
* <p>
* We construct the indices based on the current settings. 1, 2, or 4 indices will be returned with their values.
* The number of indices is stored in the 5th position of the indices array.
*
* @param v
* the value
* @param x
* the x position
* @param y
* the y position
* @param indices
* the indices
* @param values
* the values for the indices
*/
private void getValue(float v, float x, float y, int[] indices, float[] values)
{
final int x1 = (int) x;
final int y1 = (int) y;
final int index = y1 * imageWidth + x1;
if ((displayFlags & DISPLAY_WEIGHTED) == 0)
{
// No interpolation. Just put the value on the containing pixel
indices[0] = index;
values[0] = v;
indices[4] = 1;
return;
}
// Note: It is very unlikely that dx and dy will be 0.5f so we ignore this case for speed.
// It could be added later to test if speed is impacted since we return the number of indices.
// If a user wants to add data only to one pixel then they can remove the weighted option.
indices[4] = 4;
// Use bilinear weighting
final float dx = x - x1;
final float dy = y - y1;
final float wx; // X weight for the location pixel
final float wy; // Y weight for the location pixel
// Get the 4 neighbours and avoid overrun. In this case the edge pixel will get the entire value.
final int xDelta, yDelta;
// Note: The image width/height could be zero making the deltas invalid. However in this case the
// getValue(...) method will never be called.
if (dx < 0.5f)
{
// Interpolate to the lower x pixel
wx = 0.5f + dx;
if (x1 == 0)
{
xDelta = 0;
}
else
{
xDelta = -1;
}
}
else
{
// Interpolate to the upper x pixel
wx = 1.5f - dx;
if (x1 == xlimit)
{
xDelta = 0;
}
else
{
xDelta = 1;
}
}
if (dy < 0.5f)
{
// Interpolate to the lower y pixel
wy = 0.5f + dy;
if (y1 == 0)
{
yDelta = 0;
}
else
{
yDelta = -imageWidth;
}
}
else
{
// Interpolate to the upper y pixel
wy = 1.5f - dy;
if (y1 == ylimit)
{
yDelta = 0;
}
else
{
yDelta = imageWidth;
}
}
indices[0] = index;
indices[1] = index + xDelta;
indices[2] = index + yDelta;
indices[3] = index + xDelta + yDelta;
final float wxDelta = 1f - wx;
final float wyDelta = 1f - wy;
values[0] = v * wx * wy;
values[1] = v * wxDelta * wy;
values[2] = v * wx * wyDelta;
values[3] = v * wxDelta * wyDelta;
}
/**
* Simplified method to allow the image to be reconstructed using just T,X,Y coordinates and a value
*
* @param peak
* The peak frame
* @param x
* The X coordinate
* @param y
* The Y coordinate
* @param v
* The value
*/
public void add(int peak, float x, float y, float v)
{
if (!imageActive)
return;
x = mapX(x);
y = mapY(y);
// Check bounds
if (x < 0 || x >= imageWidth || y < 0 || y >= imageHeight)
return;
checkAndUpdateToFrame(peak);
int[] indices = new int[5];
float[] values = new float[4];
getValue(v, x, y, indices, values);
addData(1, indices[4], indices, values);
updateImage();
}
/**
* Simplified method to allow the image to be reconstructed using just X,Y coordinates and a value
*
* @param x
* The X coordinate
* @param y
* The Y coordinate
* @param v
* The value
*/
public void add(float x, float y, float v)
{
if (!imageActive)
return;
x = mapX(x);
y = mapY(y);
// Check bounds
if (x < 0 || x >= imageWidth || y < 0 || y >= imageHeight)
return;
int[] indices = new int[5];
float[] values = new float[4];
getValue(v, x, y, indices, values);
addData(1, indices[4], indices, values);
updateImage();
}
/**
* Simplified method to allow the image to be reconstructed using just T,X,Y coordinates and a value
*
* @param allpeak
* The peak frames
* @param allx
* The X coordinates
* @param ally
* The Y coordinates
* @param allv
* The values
*/
public void add(int[] allpeak, float[] allx, float[] ally, float[] allv)
{
if (!imageActive)
return;
int[] indices = new int[5];
float[] values = new float[4];
int nPoints = 0;
int nValues = 0;
// Buffer output in batches
int[] allIndices = new int[100];
float[] allValues = new float[allIndices.length];
boolean replace = ((displayFlags & DISPLAY_REPLACE) != 0);
// We add at most 4 indices for each peak
int limit = allIndices.length - 4;
for (int j = 0; j < allx.length; j++)
{
float x = mapX(allx[j]);
float y = mapY(ally[j]);
int peak = allpeak[j];
// Check bounds
if (x < 0 || x >= imageWidth || y < 0 || y >= imageHeight)
continue;
if (shouldUpdate(peak))
{
addData(nPoints, nValues, allIndices, allValues);
nPoints = 0;
nValues = 0;
updateToFrame(peak);
}
getValue(allv[j], x, y, indices, values);
for (int i = indices[4]; i-- > 0;)
{
allIndices[nValues] = indices[i];
allValues[nValues] = values[i];
nValues++;
}
nPoints++;
if (nValues > limit || replace)
{
addData(nPoints, nValues, allIndices, allValues);
nPoints = 0;
nValues = 0;
updateImage();
if (!imageActive)
return;
}
}
// Now add the values to the configured indices
addData(nPoints, nValues, allIndices, allValues);
updateImage();
}
/**
* Simplified method to allow the image to be reconstructed using just T,X,Y coordinates and a value
*
* @param allx
* The X coordinates
* @param ally
* The Y coordinates
* @param allv
* The values
*/
public void add(float[] allx, float[] ally, float[] allv)
{
if (!imageActive)
return;
int[] indices = new int[5];
float[] values = new float[4];
int nPoints = 0;
int nValues = 0;
// Buffer output in batches
int[] allIndices = new int[100];
float[] allValues = new float[allIndices.length];
boolean replace = ((displayFlags & DISPLAY_REPLACE) != 0);
// We add at most 4 indices for each peak
int limit = allIndices.length - 4;
for (int j = 0; j < allx.length; j++)
{
float x = mapX(allx[j]);
float y = mapY(ally[j]);
// Check bounds
if (x < 0 || x >= imageWidth || y < 0 || y >= imageHeight)
continue;
getValue(allv[j], x, y, indices, values);
for (int i = indices[4]; i-- > 0;)
{
allIndices[nValues] = indices[i];
allValues[nValues] = values[i];
nValues++;
}
nPoints++;
if (nValues > limit || replace)
{
addData(nPoints, nValues, allIndices, allValues);
nPoints = 0;
nValues = 0;
updateImage();
if (!imageActive)
return;
}
}
// Now add the values to the configured indices
addData(nPoints, nValues, allIndices, allValues);
updateImage();
}
/**
* Check if the stack should be updated to move the rolling window to the given peak.
*
* @param peak
* @return True if update is required
*/
protected boolean shouldUpdate(int peak)
{
return (rollingWindowSize > 0) && (peak >= currentFrame + rollingWindowSize);
}
/**
* Add frames to the current stack to ensure the rolling window size is enforced (i.e. batches of N are drawn as a
* frame)
*
* @param peak
*/
protected void checkAndUpdateToFrame(int peak)
{
if (shouldUpdate(peak))
updateToFrame(peak);
}
/**
* Add frames to the current stack to ensure the rolling window size is enforced (i.e. batches of N are drawn as a
* frame)
*
* @param peak
*/
protected void updateToFrame(int peak)
{
synchronized (data) // Stop other threads adding more data
{
int i = 0;
ImageStack stack = imp.getStack();
peak -= rollingWindowSize;
while (peak >= currentFrame)
{
//System.out.printf("%d => %d (%d)\n", currentFrame, currentFrame + rollingWindowSize, peak+rollingWindowSize);
if (i++ == 0)
{
// Draw all current data for first time we move forward.
// Force repaint
forceUpdateImage();
}
ImageProcessor ip = createNewProcessor(stack.getWidth(), stack.getHeight());
stack.addSlice(null, ip);
currentFrame += rollingWindowSize;
}
// Check if any frames were added
if (i > 0)
{
imp.setStack(stack);
imp.setSlice(stack.getSize());
resetData();
}
}
}
private void resetData()
{
Arrays.fill(data, EMPTY);
}
/*
* (non-Javadoc)
*
* @see gdsc.utils.fitting.results.PeakResults#addAll(java.util.Collection)
*/
public void addAll(Collection<PeakResult> results)
{
if (!imageActive)
return;
int[] indices = new int[5];
float[] values = new float[4];
int nPoints = 0;
int nValues = 0;
// Buffer output in batches
int[] allIndices = new int[100];
float[] allValues = new float[allIndices.length];
boolean replace = ((displayFlags & DISPLAY_REPLACE) != 0);
// We add at most 4 indices for each peak
int limit = allIndices.length - 4;
for (PeakResult result : results)
{
float x = mapX(result.getXPosition());
float y = mapY(result.getYPosition());
// Check bounds
if (x < 0 || x >= imageWidth || y < 0 || y >= imageHeight)
continue;
if (shouldUpdate(result.getFrame()))
{
addData(nPoints, nValues, allIndices, allValues);
nPoints = 0;
nValues = 0;
updateToFrame(result.getFrame());
}
getValue(result.getFrame(), result.params, result.error, x, y, indices, values);
for (int i = indices[4]; i-- > 0;)
{
allIndices[nValues] = indices[i];
allValues[nValues] = values[i];
nValues++;
}
nPoints++;
if (nValues > limit || replace)
{
addData(nPoints, nValues, allIndices, allValues);
nPoints = 0;
nValues = 0;
updateImage();
if (!imageActive)
return;
}
}
// Now add the values to the configured indices
addData(nPoints, nValues, allIndices, allValues);
updateImage();
}
protected void updateImage()
{
if (size < nextRepaintSize || !liveImage || !displayImage)
return;
if (!imp.isVisible())
{
//System.out.println("Image has been closed");
imageActive = false;
return;
}
if (repaintDelay != 0)
{
long time = System.currentTimeMillis();
if (time < nextPaintTime)
{
// Get the amount of time it took to acquire the data
int n = size - lastPaintSize;
long elapsed = time - (nextPaintTime - repaintDelay);
if (elapsed > 0)
{
// Set the next repaint size using linear scaling
long remaining = nextPaintTime - time;
int extra = (int) (n * ((double) remaining / elapsed));
//System.out.printf("Updating next paint size: %d : %d -> %d\n", lastPaintSize, nextRepaintSize,
// nextRepaintSize + extra);
nextRepaintSize += extra;
}
else
{
setNextRepaintSize(size);
}
return;
}
}
drawImage();
}
private void setNextRepaintSize(int size)
{
nextRepaintSize = (int) Math.ceil(size + size * repaintInterval);
}
/**
* This forces all the current data to be written to the image. It is used when a rolling window is being drawn.
*/
private void forceUpdateImage()
{
if (!imp.isVisible())
{
imageActive = false;
return;
}
drawImage();
}
private void drawImage()
{
if (aquireLock())
{
try
{
createImage();
// We direct manipulate the pixel buffer so this is not necessary
//ImageProcessor ip = imp.getProcessor();
//ip.setPixels(newPixels);
//imp.setProcessor(ip);
imp.updateAndDraw();
}
finally
{
releaseLock();
}
}
}
private synchronized boolean aquireLock()
{
if (imageLock)
return false;
imageLock = true;
return true;
}
private synchronized void releaseLock()
{
imageLock = false;
}
/*
* (non-Javadoc)
*
* @see gdsc.utils.fitting.PeakResults#size()
*/
public int size()
{
return size;
}
/*
* (non-Javadoc)
*
* @see gdsc.utils.fitting.PeakResults#end()
*/
public void end()
{
// Wait for previous image to finish rendering
while (!aquireLock())
{
try
{
System.out.printf("Waiting for final image\n");
Thread.sleep(50);
}
catch (InterruptedException e)
{
// Ignore
}
}
releaseLock();
drawImage();
if (rollingWindowSize > 0)
{
imp.setDimensions(1, 1, imp.getStackSize());
}
imageActive = false;
}
/**
* Image will be repainted when the size is increased by a fraction of the last size painted.
*
* @param repaintInterval
* the repaintInterval to set
*/
public void setRepaintInterval(double repaintInterval)
{
if (repaintInterval < 0)
repaintInterval = 0;
this.repaintInterval = repaintInterval;
}
/**
* @return the repaintInterval
*/
public double getRepaintInterval()
{
return repaintInterval;
}
/**
* Sets the repaint delay (time in milliseconds that must elapse before a repaint).
*
* @param repaintDelay
* the new repaint delay
*/
public void setRepaintDelay(long repaintDelay)
{
if (repaintDelay < 0)
repaintDelay = 0;
this.repaintDelay = repaintDelay;
}
/**
* Gets the repaint delay.
*
* @return the repaint delay
*/
public long getRepaintDelay()
{
return repaintDelay;
}
/**
* @param displayFlags
* the displayFlags to set
*/
public void setDisplayFlags(int displayFlags)
{
this.displayFlags = displayFlags;
}
/**
* @return the displayFlags
*/
public int getDisplayFlags()
{
return displayFlags;
}
/*
* (non-Javadoc)
*
* @see gdsc.utils.fitting.results.PeakResults#isActive()
*/
public boolean isActive()
{
return imageActive;
}
/**
* @return the rollingWindowSize
*/
public int getRollingWindowSize()
{
return rollingWindowSize;
}
/**
* Produce a final output image as a stack. Specify the number of peak frames to combine into each stack frame, e.g.
* a window size of 10 will combine 10 consecutive fitting frames into 1 plane.
* <p>
* This setting only applies before the {@link #begin()} method.
*
* @param rollingWindowSize
* the rollingWindowSize to set
*/
public void setRollingWindowSize(int rollingWindowSize)
{
this.rollingWindowSize = rollingWindowSize;
}
/**
* Over-ridden to ignore any passed in bounds. The bounds must be set when the image is created.
*
* @see gdsc.smlm.results.AbstractPeakResults#setBounds(java.awt.Rectangle)
*/
public void setBounds(Rectangle bounds)
{
// Ignore. Bounds are only valid when the image is created
}
/**
* @return True if the image should be displayed
*/
public boolean isDisplayImage()
{
return displayImage;
}
/**
* Set to true if the image should be displayed. Should be called before {@link #begin()}
*
* @param displayImage
* Set to true if the image should be displayed.
*/
public void setDisplayImage(boolean displayImage)
{
this.displayImage = displayImage;
}
/**
* @return The IJ image
*/
public ImagePlus getImagePlus()
{
return imp;
}
/**
* @return the lutName
*/
public String getLutName()
{
return lutName;
}
/**
* @param lutName
* the lutName to set
*/
public void setLutName(String lutName)
{
this.lutName = lutName;
}
/**
* Checks if is live image. If true the image will be updated as data is added.
*
* @return true, if is live image
*/
public boolean isLiveImage()
{
return liveImage;
}
/**
* Sets the live image flag. Set to true and the image will be updated as data is added.
*
* @param liveImage
* the new live image flag
*/
public void setLiveImage(boolean liveImage)
{
this.liveImage = liveImage;
}
}