package gdsc.smlm.ij.plugins;
import java.awt.Color;
import java.awt.Rectangle;
import java.util.ArrayList;
import gdsc.core.ij.Utils;
import gdsc.core.utils.Sort;
/*-----------------------------------------------------------------------------
* GDSC Plugins for ImageJ
*
* Copyright (C) 2011 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 2 of the License, or
* (at your option) any later version.
*---------------------------------------------------------------------------*/
import gdsc.smlm.ij.plugins.ResultsManager.InputSource;
import gdsc.smlm.results.MemoryPeakResults;
import gdsc.smlm.results.PeakResult;
import gdsc.smlm.results.Trace;
import gdsc.smlm.results.TraceManager;
import ij.IJ;
import ij.ImagePlus;
import ij.ImageStack;
import ij.WindowManager;
import ij.gui.GenericDialog;
import ij.gui.ImageWindow;
import ij.gui.Overlay;
import ij.gui.PointRoi;
import ij.gui.PolygonRoi;
import ij.gui.Roi;
import ij.plugin.PlugIn;
import ij.process.ByteProcessor;
import ij.process.FloatPolygon;
import ij.process.LUT;
import ij.process.LUTHelper;
/**
* Compares the coordinates in sets of traced results and computes the match statistics.
*/
public class DrawClusters implements PlugIn
{
private static final String TITLE = "Draw Clusters";
private static final String[] sorts = new String[] { "None", "ID", "Time", "Size", "Length", "MSD", "Mean/Frame" };
private static String inputOption = "";
private static String title = "";
private static int imageSize = 20;
private static boolean expandToSingles = false;
private static int minSize = 2;
private static int maxSize = 0;
private static boolean drawLines = true;
private static int sort = 0;
private static boolean splineFit = false;
private static boolean useStackPosition = false;
private static int lut = 0;
/*
* (non-Javadoc)
*
* @see ij.plugin.PlugIn#run(java.lang.String)
*/
public void run(String arg)
{
SMLMUsageTracker.recordPlugin(this.getClass(), arg);
if (MemoryPeakResults.isMemoryEmpty())
{
IJ.error(TITLE, "No localisations in memory");
return;
}
if (!showDialog())
return;
// Load the results
MemoryPeakResults results = ResultsManager.loadInputResults(inputOption, false);
if (results == null || results.size() == 0)
{
IJ.error(TITLE, "No results could be loaded");
return;
}
// Get the traces
Trace[] traces = TraceManager.convert(results);
if (traces == null || traces.length == 0)
{
IJ.error(TITLE, "No traces could be loaded");
return;
}
// Filter traces to a min size
int maxFrame = 0;
int count = 0;
final int myMaxSize = (maxSize < minSize) ? Integer.MAX_VALUE : maxSize;
final boolean myDrawLines = (myMaxSize < 2) ? false : drawLines;
for (int i = 0; i < traces.length; i++)
{
if (expandToSingles)
traces[i].expandToSingles();
if (traces[i].size() >= minSize && traces[i].size() <= myMaxSize)
{
traces[count++] = traces[i];
traces[i].sort();
if (maxFrame < traces[i].getTail().getFrame())
maxFrame = traces[i].getTail().getFrame();
}
}
if (count == 0)
{
IJ.error(TITLE, "No traces achieved the size limits");
return;
}
String msg = String.format(TITLE + ": %d / %s (%s)", count, Utils.pleural(traces.length, "trace"),
Utils.pleural(results.size(), "localisation"));
IJ.showStatus(msg);
//Utils.log(msg);
Rectangle bounds = results.getBounds(true);
ImagePlus imp = WindowManager.getImage(title);
boolean isUseStackPosition = useStackPosition;
if (imp == null)
{
// Create a default image using 100 pixels as the longest edge
double maxD = (bounds.width > bounds.height) ? bounds.width : bounds.height;
int w, h;
if (maxD == 0)
{
// Note that imageSize can be zero (for auto sizing)
w = h = (imageSize == 0) ? 20 : imageSize;
}
else
{
// Note that imageSize can be zero (for auto sizing)
if (imageSize == 0)
{
w = bounds.width;
h = bounds.height;
}
else
{
w = (int) (imageSize * bounds.width / maxD);
h = (int) (imageSize * bounds.height / maxD);
}
}
ByteProcessor bp = new ByteProcessor(w, h);
if (isUseStackPosition)
{
ImageStack stack = new ImageStack(w, h, maxFrame);
for (int i = 1; i <= maxFrame; i++)
stack.setPixels(bp.getPixels(), i); // Do not clone as the image is empty
imp = Utils.display(TITLE, stack);
}
else
imp = Utils.display(TITLE, bp);
// Enlarge
ImageWindow iw = imp.getWindow();
for (int i = 9; i-- > 0 && iw.getWidth() < 500 && iw.getHeight() < 500;)
{
iw.getCanvas().zoomIn(imp.getWidth() / 2, imp.getHeight() / 2);
}
}
else
{
// Check if the image has enough frames for all the traces
if (maxFrame > imp.getNFrames())
isUseStackPosition = false;
}
final float xScale = (float) (imp.getWidth() / bounds.getWidth());
final float yScale = (float) (imp.getHeight() / bounds.getHeight());
// Create ROIs and store data to sort them
Roi[] rois = new Roi[count];
int[][] frames = (isUseStackPosition) ? new int[count][] : null;
int[] indices = Utils.newArray(count, 0, 1);
double[] values = new double[count];
for (int i = 0; i < count; i++)
{
Trace trace = traces[i];
int nPoints = trace.size();
float[] xPoints = new float[nPoints];
float[] yPoints = new float[nPoints];
int j = 0;
if (isUseStackPosition)
frames[i] = new int[nPoints];
for (PeakResult result : trace.getPoints())
{
xPoints[j] = (result.getXPosition() - bounds.x) * xScale;
yPoints[j] = (result.getYPosition() - bounds.y) * yScale;
if (isUseStackPosition)
frames[i][j] = result.getFrame();
j++;
}
Roi roi;
if (myDrawLines)
{
roi = new PolygonRoi(xPoints, yPoints, nPoints, Roi.POLYLINE);
if (splineFit)
((PolygonRoi) roi).fitSpline();
}
else
{
roi = new PointRoi(xPoints, yPoints, nPoints);
((PointRoi) roi).setShowLabels(false);
}
rois[i] = roi;
switch (sort)
{
case 0:
default:
break;
case 1: // Sort by ID
values[i] = traces[i].getId();
break;
case 2: // Sort by time
values[i] = traces[i].getHead().getFrame();
break;
case 3: // Sort by size descending
values[i] = -traces[i].size();
break;
case 4: // Sort by length descending
values[i] = -roi.getLength();
break;
case 5: // Mean Square Displacement
values[i] = -traces[i].getMSD();
break;
case 6: // Mean / Frame
values[i] = -traces[i].getMeanPerFrame();
break;
}
}
if (sort > 0)
Sort.sort(indices, values);
// Draw the traces as ROIs on an overlay
Overlay o = new Overlay();
LUT lut = LUTHelper.createLUT(DrawClusters.lut);
final double scale = 256.0 / count;
if (isUseStackPosition)
{
// Add the tracks on the frames containing the results
final boolean isHyperStack = imp.isDisplayedHyperStack();
for (int i = 0; i < count; i++)
{
final int index = indices[i];
final Color c = LUTHelper.getColour(lut, (int) (i * scale));
final PolygonRoi roi = (PolygonRoi) rois[index];
roi.setFillColor(c);
roi.setStrokeColor(c);
final FloatPolygon fp = roi.getNonSplineFloatPolygon();
//final Rectangle2D.Double pos = roi.getFloatBounds();
// For each frame in the track, add the ROI track and a point ROI for the current position
for (int j = 0; j < frames[index].length; j++)
{
addToOverlay(o, (Roi) roi.clone(), isHyperStack, frames[index][j]);
//PointRoi pointRoi = new PointRoi(pos.x + fp.xpoints[j], pos.y + fp.ypoints[j]);
PointRoi pointRoi = new PointRoi(fp.xpoints[j], fp.ypoints[j]);
pointRoi.setPointType(3);
pointRoi.setFillColor(c);
pointRoi.setStrokeColor(Color.black);
addToOverlay(o, pointRoi, isHyperStack, frames[index][j]);
}
}
}
else
{
// Add the tracks as a single overlay
for (int i = 0; i < count; i++)
{
final Roi roi = rois[indices[i]];
roi.setStrokeColor(new Color(lut.getRGB((int) (i * scale))));
o.add(roi);
}
}
imp.setOverlay(o);
IJ.showStatus(msg);
}
private boolean showDialog()
{
GenericDialog gd = new GenericDialog(TITLE);
ArrayList<String> titles = new ArrayList<String>(WindowManager.getImageCount());
titles.add("[None]");
int[] idList = WindowManager.getIDList();
if (idList != null)
for (int id : idList)
{
ImagePlus imp = WindowManager.getImage(id);
if (imp != null)
titles.add(imp.getTitle());
}
gd.addMessage("Draw the clusters on an image");
ResultsManager.addInput(gd, "Input", inputOption, InputSource.MEMORY_CLUSTERED);
gd.addChoice("Image", titles.toArray(new String[0]), title);
gd.addNumericField("Image_size", imageSize, 0);
gd.addCheckbox("Expand_to_singles", expandToSingles);
gd.addSlider("Min_size", 1, 15, minSize);
gd.addSlider("Max_size", 0, 20, maxSize);
gd.addCheckbox("Traces (draw lines)", drawLines);
gd.addChoice("Sort", sorts, sorts[sort]);
gd.addCheckbox("Spline_fit (traces only)", splineFit);
gd.addCheckbox("Use_stack_position", useStackPosition);
gd.addChoice("LUT", LUTHelper.luts, LUTHelper.luts[lut]);
gd.showDialog();
if (gd.wasCanceled())
return false;
inputOption = ResultsManager.getInputSource(gd);
title = gd.getNextChoice();
imageSize = (int) Math.abs(gd.getNextNumber());
expandToSingles = gd.getNextBoolean();
minSize = (int) Math.abs(gd.getNextNumber());
maxSize = (int) Math.abs(gd.getNextNumber());
drawLines = gd.getNextBoolean();
sort = gd.getNextChoiceIndex();
splineFit = gd.getNextBoolean();
useStackPosition = gd.getNextBoolean();
lut = gd.getNextChoiceIndex();
return true;
}
private void addToOverlay(Overlay o, Roi roi, boolean isHyperStack, int frame)
{
if (isHyperStack)
roi.setPosition(0, 0, frame);
else
roi.setPosition(frame);
o.add(roi);
}
}