package gdsc.smlm.ij.plugins;
/*-----------------------------------------------------------------------------
* 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.
*---------------------------------------------------------------------------*/
import gdsc.core.ij.IJTrackProgress;
import gdsc.smlm.ij.plugins.ResultsManager.InputSource;
import gdsc.core.ij.Utils;
import gdsc.smlm.results.MemoryPeakResults;
import gdsc.smlm.results.PeakResult;
import gdsc.smlm.results.Trace;
import gdsc.smlm.results.TraceManager;
import gdsc.core.clustering.Cluster;
import gdsc.core.clustering.ClusteringAlgorithm;
import gdsc.core.clustering.ClusteringEngine;
import gdsc.core.utils.StoredData;
import gdsc.core.utils.StoredDataStatistics;
import ij.IJ;
import ij.Prefs;
import ij.gui.GenericDialog;
import ij.gui.Plot2;
import ij.plugin.PlugIn;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.math3.util.FastMath;
/**
* Computes a graph of the dark time and estimates the time threshold for the specified point in the
* cumulative histogram.
*/
public class DarkTimeAnalysis implements PlugIn
{
private static String TITLE = "Dark-time Analysis";
private static String[] METHOD;
private static ClusteringAlgorithm[] algorithms = new ClusteringAlgorithm[] {
ClusteringAlgorithm.CENTROID_LINKAGE_TIME_PRIORITY, ClusteringAlgorithm.CENTROID_LINKAGE_DISTANCE_PRIORITY,
ClusteringAlgorithm.PARTICLE_CENTROID_LINKAGE_TIME_PRIORITY, ClusteringAlgorithm.PARTICLE_CENTROID_LINKAGE_DISTANCE_PRIORITY };
static
{
ArrayList<String> methods = new ArrayList<String>();
methods.add("Tracing");
for (ClusteringAlgorithm c : algorithms)
methods.add("Clustering (" + c.toString() + ")");
METHOD = methods.toArray(new String[methods.size()]);
}
private static String inputOption = "";
private static int method = 0;
private static double msPerFrame = 50;
private static double searchDistance = 100;
private static double maxDarkTime = 0;
private static double percentile = 99;
private static int nBins = 0;
/*
* (non-Javadoc)
*
* @see ij.plugin.PlugIn#run(java.lang.String)
*/
public void run(String arg)
{
SMLMUsageTracker.recordPlugin(this.getClass(), arg);
// Require some fit results and selected regions
if (MemoryPeakResults.isMemoryEmpty())
{
IJ.error(TITLE, "There are no fitting results in memory");
return;
}
if (!showDialog())
return;
MemoryPeakResults results = ResultsManager.loadInputResults(inputOption, true);
if (results == null || results.size() == 0)
{
IJ.error(TITLE, "No results could be loaded");
IJ.showStatus("");
return;
}
msPerFrame = results.getCalibration().getExposureTime();
Utils.log("%s: %d localisations", TITLE, results.size());
if (results.size() == 0)
{
IJ.error(TITLE, "No results were loaded");
return;
}
analyse(results);
}
private boolean showDialog()
{
GenericDialog gd = new GenericDialog(TITLE);
gd.addHelp(About.HELP_URL);
gd.addMessage("Compute the cumulative dark-time histogram");
ResultsManager.addInput(gd, inputOption, InputSource.MEMORY);
gd.addChoice("Method", METHOD, METHOD[method]);
gd.addSlider("Search_distance (nm)", 5, 150, searchDistance);
gd.addNumericField("Max_dark_time (seconds)", maxDarkTime, 2);
gd.addSlider("Percentile", 0, 100, percentile);
gd.addSlider("Histogram_bins", -1, 100, nBins);
gd.showDialog();
if (gd.wasCanceled())
return false;
inputOption = gd.getNextChoice();
method = gd.getNextChoiceIndex();
searchDistance = gd.getNextNumber();
maxDarkTime = gd.getNextNumber();
percentile = gd.getNextNumber();
nBins = (int) gd.getNextNumber();
// Check arguments
try
{
Parameters.isAboveZero("Search distance", searchDistance);
Parameters.isPositive("Percentile", percentile);
}
catch (IllegalArgumentException e)
{
IJ.error(TITLE, e.getMessage());
return false;
}
return true;
}
private void analyse(MemoryPeakResults results)
{
// Find min and max time frames
results.sort();
int min = results.getHead().getFrame();
int max = results.getTail().getEndFrame();
// Trace results
double d = searchDistance / results.getCalibration().getNmPerPixel();
int range = max - min + 1;
if (maxDarkTime > 0)
range = FastMath.max(1, (int) Math.round(maxDarkTime * 1000 / msPerFrame));
IJTrackProgress tracker = new IJTrackProgress();
tracker.status("Analysing ...");
tracker.log("Analysing (d=%s nm (%s px) t=%s s (%d frames)) ...", Utils.rounded(searchDistance),
Utils.rounded(d), Utils.rounded(range * msPerFrame / 1000.0), range);
Trace[] traces;
if (method == 0)
{
TraceManager tm = new TraceManager(results);
tm.setTracker(tracker);
tm.traceMolecules(d, range);
traces = tm.getTraces();
}
else
{
ClusteringEngine engine = new ClusteringEngine(Prefs.getThreads(), algorithms[method - 1], tracker);
List<PeakResult> peakResults = results.getResults();
ArrayList<Cluster> clusters = engine.findClusters(TraceMolecules.convertToClusterPoints(peakResults), d,
range);
traces = TraceMolecules.convertToTraces(peakResults, clusters);
}
tracker.status("Computing histogram ...");
// Build dark-time histogram
int[] times = new int[range];
StoredData stats = new StoredData();
for (Trace trace : traces)
{
if (trace.getNBlinks() > 1)
{
for (int t : trace.getOffTimes())
{
times[t]++;
}
stats.add(trace.getOffTimes());
}
}
plotDarkTimeHistogram(stats);
// Cumulative histogram
for (int i = 1; i < times.length; i++)
times[i] += times[i - 1];
int total = times[times.length - 1];
// Plot dark-time up to 100%
double[] x = new double[range];
double[] y = new double[range];
int truncate = 0;
for (int i = 0; i < x.length; i++)
{
x[i] = i * msPerFrame;
y[i] = (100.0 * times[i]) / total;
if (times[i] == total) // 100%
{
truncate = i + 1;
break;
}
}
if (truncate > 0)
{
x = Arrays.copyOf(x, truncate);
y = Arrays.copyOf(y, truncate);
}
String title = "Cumulative Dark-time";
Plot2 plot = new Plot2(title, "Time (ms)", "Percentile", x, y);
Utils.display(title, plot);
// Report percentile
for (int i = 0; i < y.length; i++)
{
if (y[i] >= percentile)
{
Utils.log("Dark-time Percentile %.1f @ %s ms = %s s", percentile, Utils.rounded(x[i]),
Utils.rounded(x[i] / 1000));
break;
}
}
tracker.status("");
}
private void plotDarkTimeHistogram(StoredData stats)
{
if (nBins >= 0)
{
// Convert the X-axis to milliseconds
double[] xValues = stats.getValues();
for (int i = 0; i < xValues.length; i++)
xValues[i] *= msPerFrame;
// Ensure the bin width is never less than 1
Utils.showHistogram("Dark-time", new StoredDataStatistics(xValues), "Time (ms)", 1, 0, nBins);
}
}
}