use of uk.ac.sussex.gdsc.core.utils.StoredDataStatistics in project GDSC-SMLM by aherbert.
the class CreateData method showSummary.
private double showSummary(List<? extends FluorophoreSequenceModel> fluorophores, List<LocalisationModel> localisations) {
IJ.showStatus("Calculating statistics ...");
final Statistics[] stats = new Statistics[NAMES.length];
for (int i = 0; i < stats.length; i++) {
stats[i] = (settings.getShowHistograms() || alwaysRemoveOutliers[i]) ? new StoredDataStatistics() : new Statistics();
}
// Find the largest timepoint
final ImagePlus outputImp = WindowManager.getImage(benchmarkImageId);
int frameCount;
if (outputImp == null) {
sortLocalisationsByTime(localisations);
frameCount = localisations.get(localisations.size() - 1).getTime();
} else {
frameCount = outputImp.getStackSize();
}
final int[] countHistogram = new int[frameCount + 1];
// Use the localisations that were drawn to create the sampled on/off times
rebuildNeighbours(localisations);
// Assume that there is at least one localisation
final LocalisationModel first = localisations.get(0);
// The current localisation
int currentId = first.getId();
// The last time this localisation was on
int lastT = first.getTime();
// Number of blinks
int blinks = 0;
// On-time of current pulse
int currentT = 0;
double signal = 0;
final double centreOffset = settings.getSize() * 0.5;
// Used to convert the sampled times in frames into seconds
final double framesPerSecond = 1000.0 / settings.getExposureTime();
// final double gain = new CreateDataSettingsHelper(settings).getTotalGainSafe();
for (final LocalisationModel l : localisations) {
final double[] data = l.getData();
if (data == null) {
throw new IllegalStateException("No localisation data. This should not happen!");
}
final double noise = data[1];
final double sx = data[2];
final double sy = data[3];
final double intensityInPhotons = data[4];
// Q. What if the noise is zero, i.e. no background photon / read noise?
// Just ignore it at current. This is only an approximation to the SNR estimate
// if this is not a Gaussian spot.
final double snr = Gaussian2DPeakResultHelper.getMeanSignalUsingP05(intensityInPhotons, sx, sy) / noise;
stats[SIGNAL].add(intensityInPhotons);
stats[NOISE].add(noise);
if (noise != 0) {
stats[SNR].add(snr);
}
// if (l.isContinuous())
if (l.getNext() != null && l.getPrevious() != null) {
stats[SIGNAL_CONTINUOUS].add(intensityInPhotons);
if (noise != 0) {
stats[SNR_CONTINUOUS].add(snr);
}
}
final int id = l.getId();
// Check if this a new fluorophore
if (currentId != id) {
// Add previous fluorophore
stats[SAMPLED_BLINKS].add(blinks);
stats[SAMPLED_T_ON].add(currentT / framesPerSecond);
stats[TOTAL_SIGNAL].add(signal);
// Reset
blinks = 0;
currentT = 1;
currentId = id;
signal = intensityInPhotons;
} else {
signal += intensityInPhotons;
// Check if the current fluorophore pulse is broken (i.e. a blink)
if (l.getTime() - 1 > lastT) {
blinks++;
stats[SAMPLED_T_ON].add(currentT / framesPerSecond);
currentT = 1;
stats[SAMPLED_T_OFF].add(((l.getTime() - 1) - lastT) / framesPerSecond);
} else {
// Continuous on-time
currentT++;
}
}
lastT = l.getTime();
countHistogram[lastT]++;
stats[X].add((l.getX() - centreOffset) * settings.getPixelPitch());
stats[Y].add((l.getY() - centreOffset) * settings.getPixelPitch());
stats[Z].add(l.getZ() * settings.getPixelPitch());
}
// Final fluorophore
stats[SAMPLED_BLINKS].add(blinks);
stats[SAMPLED_T_ON].add(currentT / framesPerSecond);
stats[TOTAL_SIGNAL].add(signal);
// Samples per frame
for (int t = 1; t < countHistogram.length; t++) {
stats[SAMPLES].add(countHistogram[t]);
}
if (fluorophores != null) {
for (final FluorophoreSequenceModel f : fluorophores) {
stats[BLINKS].add(f.getNumberOfBlinks());
// On-time
for (final double t : f.getOnTimes()) {
stats[T_ON].add(t);
}
// Off-time
for (final double t : f.getOffTimes()) {
stats[T_OFF].add(t);
}
}
} else {
// show no blinks
stats[BLINKS].add(0);
stats[T_ON].add(1);
}
if (results != null) {
// Convert depth-of-field to pixels
final double depth = settings.getDepthOfField() / settings.getPixelPitch();
try {
// Get widths
final WidthResultProcedure wp = new WidthResultProcedure(results, DistanceUnit.PIXEL);
wp.getW();
stats[WIDTH].add(wp.wx);
} catch (final DataException ex) {
ImageJUtils.log("Unable to compute width: " + ex.getMessage());
}
try {
// Get z depth
final StandardResultProcedure sp = new StandardResultProcedure(results, DistanceUnit.PIXEL);
sp.getXyz();
// Get precision
final PrecisionResultProcedure pp = new PrecisionResultProcedure(results);
pp.getPrecision();
stats[PRECISION].add(pp.precisions);
for (int i = 0; i < pp.size(); i++) {
if (Math.abs(sp.z[i]) < depth) {
stats[PRECISION_IN_FOCUS].add(pp.precisions[i]);
}
}
} catch (final DataException ex) {
ImageJUtils.log("Unable to compute LSE precision: " + ex.getMessage());
}
// Compute density per frame. Multi-thread for speed
if (settings.getDensityRadius() > 0) {
final int threadCount = Prefs.getThreads();
final Ticker ticker = ImageJUtils.createTicker(results.getLastFrame(), threadCount, "Calculating density ...");
final ExecutorService threadPool = Executors.newFixedThreadPool(threadCount);
final List<Future<?>> futures = new LinkedList<>();
final TFloatArrayList coordsX = new TFloatArrayList();
final TFloatArrayList coordsY = new TFloatArrayList();
final Statistics densityStats = stats[DENSITY];
final float radius = (float) (settings.getDensityRadius() * getHwhm());
final Rectangle bounds = results.getBounds();
final double area = (double) bounds.width * bounds.height;
// Store the density for each result.
final int[] allDensity = new int[results.size()];
final FrameCounter counter = results.newFrameCounter();
results.forEach((PeakResultProcedure) result -> {
if (counter.advance(result.getFrame())) {
counter.increment(runDensityCalculation(threadPool, futures, coordsX, coordsY, densityStats, radius, area, allDensity, counter.getCount(), ticker));
}
coordsX.add(result.getXPosition());
coordsY.add(result.getYPosition());
});
runDensityCalculation(threadPool, futures, coordsX, coordsY, densityStats, radius, area, allDensity, counter.getCount(), ticker);
ConcurrencyUtils.waitForCompletionUnchecked(futures);
threadPool.shutdown();
ImageJUtils.finished();
// Split results into singles (density = 0) and clustered (density > 0)
final MemoryPeakResults singles = copyMemoryPeakResults("No Density");
final MemoryPeakResults clustered = copyMemoryPeakResults("Density");
counter.reset();
results.forEach((PeakResultProcedure) result -> {
final int density = allDensity[counter.getAndIncrement()];
result.setOrigValue(density);
if (density == 0) {
singles.add(result);
} else {
clustered.add(result);
}
});
}
}
final StringBuilder sb = new StringBuilder();
sb.append(datasetNumber).append('\t');
if (settings.getCameraType() == CameraType.SCMOS) {
sb.append("sCMOS (").append(settings.getCameraModelName()).append(") ");
final Rectangle bounds = cameraModel.getBounds();
sb.append(" ").append(bounds.x).append(",").append(bounds.y);
final int size = settings.getSize();
sb.append(" ").append(size).append("x").append(size);
} else if (CalibrationProtosHelper.isCcdCameraType(settings.getCameraType())) {
sb.append(CalibrationProtosHelper.getName(settings.getCameraType()));
final int size = settings.getSize();
sb.append(" ").append(size).append("x").append(size);
if (settings.getCameraType() == CameraType.EMCCD) {
sb.append(" EM=").append(settings.getEmGain());
}
sb.append(" CG=").append(settings.getCameraGain());
sb.append(" RN=").append(settings.getReadNoise());
sb.append(" B=").append(settings.getBias());
} else {
throw new IllegalStateException();
}
sb.append(" QE=").append(settings.getQuantumEfficiency()).append('\t');
sb.append(settings.getPsfModel());
if (psfModelType == PSF_MODEL_IMAGE) {
sb.append(" Image").append(settings.getPsfImageName());
} else if (psfModelType == PSF_MODEL_ASTIGMATISM) {
sb.append(" model=").append(settings.getAstigmatismModel());
} else {
sb.append(" DoF=").append(MathUtils.rounded(settings.getDepthOfFocus()));
if (settings.getEnterWidth()) {
sb.append(" SD=").append(MathUtils.rounded(settings.getPsfSd()));
} else {
sb.append(" λ=").append(MathUtils.rounded(settings.getWavelength()));
sb.append(" NA=").append(MathUtils.rounded(settings.getNumericalAperture()));
}
}
sb.append('\t');
sb.append((fluorophores == null) ? localisations.size() : fluorophores.size()).append('\t');
sb.append(stats[SAMPLED_BLINKS].getN() + (int) stats[SAMPLED_BLINKS].getSum()).append('\t');
sb.append(localisations.size()).append('\t');
sb.append(frameCount).append('\t');
sb.append(MathUtils.rounded(areaInUm)).append('\t');
sb.append(MathUtils.rounded(localisations.size() / (areaInUm * frameCount), 4)).append('\t');
sb.append(MathUtils.rounded(getHwhm(), 4)).append('\t');
double sd = getPsfSd();
sb.append(MathUtils.rounded(sd, 4)).append('\t');
sd *= settings.getPixelPitch();
final double sa = PsfCalculator.squarePixelAdjustment(sd, settings.getPixelPitch()) / settings.getPixelPitch();
sb.append(MathUtils.rounded(sa, 4)).append('\t');
// Width not valid for the Image PSF.
// Q. Is this true? We can approximate the FHWM for a spot-like image PSF.
final int nStats = (psfModelType == PSF_MODEL_IMAGE) ? stats.length - 1 : stats.length;
for (int i = 0; i < nStats; i++) {
final double centre = (alwaysRemoveOutliers[i]) ? ((StoredDataStatistics) stats[i]).getStatistics().getPercentile(50) : stats[i].getMean();
sb.append(MathUtils.rounded(centre, 4)).append('\t');
}
createSummaryTable().accept(sb.toString());
// Show histograms
if (settings.getShowHistograms() && !java.awt.GraphicsEnvironment.isHeadless()) {
IJ.showStatus("Calculating histograms ...");
final boolean[] chosenHistograms = getChoosenHistograms();
final WindowOrganiser wo = new WindowOrganiser();
final HistogramPlotBuilder builder = new HistogramPlotBuilder(TITLE);
for (int i = 0; i < NAMES.length; i++) {
if (chosenHistograms[i]) {
builder.setData((StoredDataStatistics) stats[i]).setName(NAMES[i]).setIntegerBins(integerDisplay[i]).setRemoveOutliersOption((settings.getRemoveOutliers() || alwaysRemoveOutliers[i]) ? 2 : 0).setNumberOfBins(settings.getHistogramBins()).show(wo);
}
}
wo.tile();
}
IJ.showStatus("");
return stats[SIGNAL].getMean();
}
use of uk.ac.sussex.gdsc.core.utils.StoredDataStatistics in project GDSC-SMLM by aherbert.
the class CreateData method run.
@Override
public void run(String arg) {
SmlmUsageTracker.recordPlugin(this.getClass(), arg);
extraOptions = ImageJUtils.isExtraOptions();
simpleMode = (arg != null && arg.contains("simple"));
benchmarkMode = (arg != null && arg.contains("benchmark"));
spotMode = (arg != null && arg.contains("spot"));
trackMode = (arg != null && arg.contains("track"));
if ("load".equals(arg)) {
loadBenchmarkData();
return;
}
// Each localisation set is a collection of localisations that represent all localisations
// with the same ID that are on in the same image time frame (Note: the simulation
// can create many localisations per fluorophore per time frame which is useful when
// modelling moving particles)
List<LocalisationModelSet> localisationSets = null;
// Each fluorophore contains the on and off times when light was emitted
List<? extends FluorophoreSequenceModel> fluorophores = null;
if (simpleMode || benchmarkMode || spotMode) {
if (!showSimpleDialog()) {
return;
}
resetMemory();
// 1 second frames
settings.setExposureTime(1000);
areaInUm = settings.getSize() * settings.getPixelPitch() * settings.getSize() * settings.getPixelPitch() / 1e6;
// Number of spots per frame
int count = 0;
int[] nextN = null;
SpatialDistribution dist;
if (benchmarkMode) {
// --------------------
// BENCHMARK SIMULATION
// --------------------
// Draw the same point on the image repeatedly
count = 1;
dist = createFixedDistribution();
try {
reportAndSaveFittingLimits(dist);
} catch (final Exception ex) {
// This will be from the computation of the CRLB
IJ.error(TITLE, ex.getMessage());
return;
}
} else if (spotMode) {
// ---------------
// SPOT SIMULATION
// ---------------
// The spot simulation draws 0 or 1 random point per frame.
// Ensure we have 50% of the frames with a spot.
nextN = new int[settings.getParticles() * 2];
Arrays.fill(nextN, 0, settings.getParticles(), 1);
RandomUtils.shuffle(nextN, UniformRandomProviders.create());
// Only put spots in the central part of the image
final double border = settings.getSize() / 4.0;
dist = createUniformDistribution(border);
} else {
// -----------------
// SIMPLE SIMULATION
// -----------------
// The simple simulation draws n random points per frame to achieve a specified density.
// No points will appear in multiple frames.
// Each point has a random number of photons sampled from a range.
// We can optionally use a mask. Create his first as it updates the areaInUm
dist = createDistribution();
// Randomly sample (i.e. not uniform density in all frames)
if (settings.getSamplePerFrame()) {
final double mean = areaInUm * settings.getDensity();
ImageJUtils.log("Mean samples = %f", mean);
if (mean < 0.5) {
final GenericDialog gd = new GenericDialog(TITLE);
gd.addMessage("The mean samples per frame is low: " + MathUtils.rounded(mean) + "\n \nContinue?");
gd.enableYesNoCancel();
gd.hideCancelButton();
gd.showDialog();
if (!gd.wasOKed()) {
return;
}
}
final PoissonSampler poisson = new PoissonSampler(createRandomGenerator(), mean);
final StoredDataStatistics samples = new StoredDataStatistics(settings.getParticles());
while (samples.getSum() < settings.getParticles()) {
samples.add(poisson.sample());
}
nextN = new int[samples.getN()];
for (int i = 0; i < nextN.length; i++) {
nextN[i] = (int) samples.getValue(i);
}
} else {
// Use the density to get the number per frame
count = (int) Math.max(1, Math.round(areaInUm * settings.getDensity()));
}
}
UniformRandomProvider rng = null;
localisationSets = new ArrayList<>(settings.getParticles());
final int minPhotons = (int) settings.getPhotonsPerSecond();
final int range = (int) settings.getPhotonsPerSecondMaximum() - minPhotons + 1;
if (range > 1) {
rng = createRandomGenerator();
}
// Add frames at the specified density until the number of particles has been reached
int id = 0;
int time = 0;
while (id < settings.getParticles()) {
// Allow the number per frame to be specified
if (nextN != null) {
if (time >= nextN.length) {
break;
}
count = nextN[time];
}
// Simulate random positions in the frame for the specified density
time++;
for (int j = 0; j < count; j++) {
final double[] xyz = dist.next();
// Ignore within border. We do not want to draw things we cannot fit.
// if (!distBorder.isWithinXy(xyz))
// continue;
// Simulate random photons
final int intensity = minPhotons + ((rng != null) ? rng.nextInt(range) : 0);
final LocalisationModel m = new LocalisationModel(id, time, xyz, intensity, LocalisationModel.CONTINUOUS);
// Each localisation can be a separate localisation set
final LocalisationModelSet set = new LocalisationModelSet(id, time);
set.add(m);
localisationSets.add(set);
id++;
}
}
} else {
if (!showDialog()) {
return;
}
resetMemory();
areaInUm = settings.getSize() * settings.getPixelPitch() * settings.getSize() * settings.getPixelPitch() / 1e6;
int totalSteps;
double correlation = 0;
ImageModel imageModel;
if (trackMode) {
// ----------------
// TRACK SIMULATION
// ----------------
// In track mode we create fixed lifetime fluorophores that do not overlap in time.
// This is the simplest simulation to test moving molecules.
settings.setSeconds((int) Math.ceil(settings.getParticles() * (settings.getExposureTime() + settings.getTOn()) / 1000));
totalSteps = 0;
final double simulationStepsPerFrame = (settings.getStepsPerSecond() * settings.getExposureTime()) / 1000.0;
imageModel = new FixedLifetimeImageModel(settings.getStepsPerSecond() * settings.getTOn() / 1000.0, simulationStepsPerFrame, createRandomGenerator());
} else {
// ---------------
// FULL SIMULATION
// ---------------
// The full simulation draws n random points in space.
// The same molecule may appear in multiple frames, move and blink.
//
// Points are modelled as fluorophores that must be activated and then will
// blink and photo-bleach. The molecules may diffuse and this can be simulated
// with many steps per image frame. All steps from a frame are collected
// into a localisation set which can be drawn on the output image.
final SpatialIllumination activationIllumination = createIllumination(settings.getPulseRatio(), settings.getPulseInterval());
// Generate additional frames so that each frame has the set number of simulation steps
totalSteps = (int) Math.ceil(settings.getSeconds() * settings.getStepsPerSecond());
// Since we have an exponential decay of activations
// ensure half of the particles have activated by 30% of the frames.
final double eAct = totalSteps * 0.3 * activationIllumination.getAveragePhotons();
// Q. Does tOn/tOff change depending on the illumination strength?
imageModel = new ActivationEnergyImageModel(eAct, activationIllumination, settings.getStepsPerSecond() * settings.getTOn() / 1000.0, settings.getStepsPerSecond() * settings.getTOffShort() / 1000.0, settings.getStepsPerSecond() * settings.getTOffLong() / 1000.0, settings.getNBlinksShort(), settings.getNBlinksLong(), createRandomGenerator());
imageModel.setUseGeometricDistribution(settings.getNBlinksGeometricDistribution());
// Only use the correlation if selected for the distribution
if (PHOTON_DISTRIBUTION[PHOTON_CORRELATED].equals(settings.getPhotonDistribution())) {
correlation = settings.getCorrelation();
}
}
imageModel.setPhotonBudgetPerFrame(true);
imageModel.setDiffusion2D(settings.getDiffuse2D());
imageModel.setRotation2D(settings.getRotate2D());
IJ.showStatus("Creating molecules ...");
final SpatialDistribution distribution = createDistribution();
final List<CompoundMoleculeModel> compounds = createCompoundMolecules();
if (compounds == null) {
return;
}
final List<CompoundMoleculeModel> molecules = imageModel.createMolecules(compounds, settings.getParticles(), distribution, settings.getRotateInitialOrientation());
// Activate fluorophores
IJ.showStatus("Creating fluorophores ...");
// Note: molecules list will be converted to compounds containing fluorophores
fluorophores = imageModel.createFluorophores(molecules, totalSteps);
if (fluorophores.isEmpty()) {
IJ.error(TITLE, "No fluorophores created");
return;
}
// Map the fluorophore ID to the compound for mixtures
if (compounds.size() > 1) {
idToCompound = new TIntIntHashMap(fluorophores.size());
for (final FluorophoreSequenceModel l : fluorophores) {
idToCompound.put(l.getId(), l.getLabel());
}
}
IJ.showStatus("Creating localisations ...");
// TODO - Output a molecule Id for each fluorophore if using compound molecules. This allows
// analysis
// of the ratio of trimers, dimers, monomers, etc that could be detected.
totalSteps = checkTotalSteps(totalSteps, fluorophores);
if (totalSteps == 0) {
return;
}
imageModel.setPhotonDistribution(createPhotonDistribution());
try {
imageModel.setConfinementDistribution(createConfinementDistribution());
} catch (final ConfigurationException ex) {
// We asked the user if it was OK to continue and they said no
return;
}
// This should be optimised
imageModel.setConfinementAttempts(10);
final List<LocalisationModel> localisations = imageModel.createImage(molecules, settings.getFixedFraction(), totalSteps, settings.getPhotonsPerSecond() / settings.getStepsPerSecond(), correlation, settings.getRotateDuringSimulation());
// Re-adjust the fluorophores to the correct time
if (settings.getStepsPerSecond() != 1) {
final double scale = 1.0 / settings.getStepsPerSecond();
for (final FluorophoreSequenceModel f : fluorophores) {
f.adjustTime(scale);
}
}
// Integrate the frames
localisationSets = combineSimulationSteps(localisations);
localisationSets = filterToImageBounds(localisationSets);
}
datasetNumber.getAndIncrement();
final List<LocalisationModel> localisations = drawImage(localisationSets);
if (localisations == null || localisations.isEmpty()) {
IJ.error(TITLE, "No localisations created");
return;
}
fluorophores = removeFilteredFluorophores(fluorophores, localisations);
final double signalPerFrame = showSummary(fluorophores, localisations);
if (!benchmarkMode) {
final boolean fullSimulation = (!(simpleMode || spotMode));
saveSimulationParameters(localisations.size(), fullSimulation, signalPerFrame);
}
IJ.showStatus("Saving data ...");
saveFluorophores(fluorophores);
saveImageResults(results);
saveLocalisations(localisations);
// The settings for the filenames may have changed
SettingsManager.writeSettings(settings.build());
IJ.showStatus("Done");
}
use of uk.ac.sussex.gdsc.core.utils.StoredDataStatistics in project GDSC-SMLM by aherbert.
the class BenchmarkSpotFit method showDoubleHistogram.
private double[] showDoubleHistogram(StoredDataStatistics[][] stats, final int index, WindowOrganiser wo, double[][] matchScores) {
final String xLabel = filterCriteria[index].name;
LowerLimit lower = filterCriteria[index].lower;
UpperLimit upper = filterCriteria[index].upper;
double[] jaccard = null;
double[] metric = null;
double maxJaccard = 0;
if (index <= FILTER_PRECISION && (settings.showFilterScoreHistograms || upper.requiresJaccard || lower.requiresJaccard)) {
// Jaccard score verses the range of the metric
for (final double[] d : matchScores) {
if (!Double.isFinite(d[index])) {
System.out.printf("Error in fit data [%d]: %s%n", index, d[index]);
}
}
// Do not use Double.compare(double, double) so we get exceptions in the sort for inf/nan
Arrays.sort(matchScores, (o1, o2) -> {
if (o1[index] < o2[index]) {
return -1;
}
if (o1[index] > o2[index]) {
return 1;
}
return 0;
});
final int scoreIndex = FILTER_PRECISION + 1;
final int n = results.size();
double tp = 0;
double fp = 0;
jaccard = new double[matchScores.length + 1];
metric = new double[jaccard.length];
for (int k = 0; k < matchScores.length; k++) {
final double score = matchScores[k][scoreIndex];
tp += score;
fp += (1 - score);
jaccard[k + 1] = tp / (fp + n);
metric[k + 1] = matchScores[k][index];
}
metric[0] = metric[1];
maxJaccard = MathUtils.max(jaccard);
if (settings.showFilterScoreHistograms) {
final String title = TITLE + " Jaccard " + xLabel;
final Plot plot = new Plot(title, xLabel, "Jaccard");
plot.addPoints(metric, jaccard, Plot.LINE);
// Remove outliers
final double[] limitsx = MathUtils.limits(metric);
final Percentile p = new Percentile();
final double l = p.evaluate(metric, 25);
final double u = p.evaluate(metric, 75);
final double iqr = 1.5 * (u - l);
limitsx[1] = Math.min(limitsx[1], u + iqr);
plot.setLimits(limitsx[0], limitsx[1], 0, MathUtils.max(jaccard));
ImageJUtils.display(title, plot, wo);
}
}
// [0] is all
// [1] is matches
// [2] is no match
final StoredDataStatistics s1 = stats[0][index];
final StoredDataStatistics s2 = stats[1][index];
final StoredDataStatistics s3 = stats[2][index];
if (s1.getN() == 0) {
return new double[4];
}
final DescriptiveStatistics d = s1.getStatistics();
double median = 0;
Plot plot = null;
String title = null;
if (settings.showFilterScoreHistograms) {
median = d.getPercentile(50);
final String label = String.format("n = %d. Median = %s nm", s1.getN(), MathUtils.rounded(median));
final HistogramPlot histogramPlot = new HistogramPlotBuilder(TITLE, s1, xLabel).setMinBinWidth(filterCriteria[index].minBinWidth).setRemoveOutliersOption((filterCriteria[index].restrictRange) ? 1 : 0).setPlotLabel(label).build();
final PlotWindow plotWindow = histogramPlot.show(wo);
if (plotWindow == null) {
IJ.log("Failed to show the histogram: " + xLabel);
return new double[4];
}
title = plotWindow.getTitle();
// Reverse engineer the histogram settings
plot = histogramPlot.getPlot();
final double[] xvalues = histogramPlot.getPlotXValues();
final int bins = xvalues.length;
final double yMin = xvalues[0];
final double binSize = xvalues[1] - xvalues[0];
final double yMax = xvalues[0] + (bins - 1) * binSize;
if (s2.getN() > 0) {
final double[] values = s2.getValues();
final double[][] hist = HistogramPlot.calcHistogram(values, yMin, yMax, bins);
if (hist[0].length > 0) {
plot.setColor(Color.red);
plot.addPoints(hist[0], hist[1], Plot.BAR);
ImageJUtils.display(title, plot);
}
}
if (s3.getN() > 0) {
final double[] values = s3.getValues();
final double[][] hist = HistogramPlot.calcHistogram(values, yMin, yMax, bins);
if (hist[0].length > 0) {
plot.setColor(Color.blue);
plot.addPoints(hist[0], hist[1], Plot.BAR);
ImageJUtils.display(title, plot);
}
}
}
// Do cumulative histogram
final double[][] h1 = MathUtils.cumulativeHistogram(s1.getValues(), true);
final double[][] h2 = MathUtils.cumulativeHistogram(s2.getValues(), true);
final double[][] h3 = MathUtils.cumulativeHistogram(s3.getValues(), true);
if (settings.showFilterScoreHistograms) {
title = TITLE + " Cumul " + xLabel;
plot = new Plot(title, xLabel, "Frequency");
// Find limits
double[] xlimit = MathUtils.limits(h1[0]);
xlimit = MathUtils.limits(xlimit, h2[0]);
xlimit = MathUtils.limits(xlimit, h3[0]);
// Restrict using the inter-quartile range
if (filterCriteria[index].restrictRange) {
final double q1 = d.getPercentile(25);
final double q2 = d.getPercentile(75);
final double iqr = (q2 - q1) * 2.5;
xlimit[0] = MathUtils.max(xlimit[0], median - iqr);
xlimit[1] = MathUtils.min(xlimit[1], median + iqr);
}
plot.setLimits(xlimit[0], xlimit[1], 0, 1.05);
plot.addPoints(h1[0], h1[1], Plot.LINE);
plot.setColor(Color.red);
plot.addPoints(h2[0], h2[1], Plot.LINE);
plot.setColor(Color.blue);
plot.addPoints(h3[0], h3[1], Plot.LINE);
}
// Determine the maximum difference between the TP and FP
double maxx1 = 0;
double maxx2 = 0;
double max1 = 0;
double max2 = 0;
// We cannot compute the delta histogram, or use percentiles
if (s2.getN() == 0) {
upper = UpperLimit.ZERO;
lower = LowerLimit.ZERO;
}
final boolean requireLabel = (settings.showFilterScoreHistograms && filterCriteria[index].requireLabel);
if (requireLabel || upper.requiresDeltaHistogram() || lower.requiresDeltaHistogram()) {
if (s2.getN() != 0 && s3.getN() != 0) {
final LinearInterpolator li = new LinearInterpolator();
final PolynomialSplineFunction f1 = li.interpolate(h2[0], h2[1]);
final PolynomialSplineFunction f2 = li.interpolate(h3[0], h3[1]);
for (final double x : h1[0]) {
if (x < h2[0][0] || x < h3[0][0]) {
continue;
}
try {
final double v1 = f1.value(x);
final double v2 = f2.value(x);
final double diff = v2 - v1;
if (diff > 0) {
if (max1 < diff) {
max1 = diff;
maxx1 = x;
}
} else if (max2 > diff) {
max2 = diff;
maxx2 = x;
}
} catch (final OutOfRangeException ex) {
// Because we reached the end
break;
}
}
}
}
if (plot != null) {
// We use bins=1 on charts where we do not need a label
if (requireLabel) {
final String label = String.format("Max+ %s @ %s, Max- %s @ %s", MathUtils.rounded(max1), MathUtils.rounded(maxx1), MathUtils.rounded(max2), MathUtils.rounded(maxx2));
plot.setColor(Color.black);
plot.addLabel(0, 0, label);
}
ImageJUtils.display(title, plot, wo);
}
// Now compute the bounds using the desired limit
double lowerBound;
double upperBound;
switch(lower) {
case MAX_NEGATIVE_CUMUL_DELTA:
// Switch to percentiles if we have no delta histogram
if (maxx2 < 0) {
lowerBound = maxx2;
break;
}
// fall-through
case ONE_PERCENT:
lowerBound = getPercentile(h2, 0.01);
break;
case MIN:
lowerBound = getPercentile(h2, 0.0);
break;
case ZERO:
lowerBound = 0;
break;
case HALF_MAX_JACCARD_VALUE:
lowerBound = getXValue(metric, jaccard, maxJaccard * 0.5);
break;
default:
throw new IllegalStateException("Missing lower limit method");
}
switch(upper) {
case MAX_POSITIVE_CUMUL_DELTA:
// Switch to percentiles if we have no delta histogram
if (maxx1 > 0) {
upperBound = maxx1;
break;
}
// fall-through
case NINETY_NINE_PERCENT:
upperBound = getPercentile(h2, 0.99);
break;
case NINETY_NINE_NINE_PERCENT:
upperBound = getPercentile(h2, 0.999);
break;
case ZERO:
upperBound = 0;
break;
case MAX_JACCARD2:
upperBound = getXValue(metric, jaccard, maxJaccard) * 2;
// System.out.printf("MaxJ = %.4f @ %.3f\n", maxJ, u / 2);
break;
default:
throw new IllegalStateException("Missing upper limit method");
}
final double min = getPercentile(h1, 0);
final double max = getPercentile(h1, 1);
return new double[] { lowerBound, upperBound, min, max };
}
use of uk.ac.sussex.gdsc.core.utils.StoredDataStatistics in project GDSC-SMLM by aherbert.
the class DoubletAnalysis method summariseResults.
/**
* Summarise results.
*
* @param results the results
* @param density the density
* @param runTime the run time
*/
private void summariseResults(ArrayList<DoubletResult> results, double density, long runTime) {
// If we are only assessing results with no neighbour candidates
// TODO - Count the number of actual results that have no neighbours
final int numberOfMolecules = this.results.size() - ignored.get();
final FitConfiguration fitConfig = config.getFitConfiguration();
// Store details we want in the analysis table
final StringBuilder sb = new StringBuilder();
sb.append(MathUtils.rounded(density)).append('\t');
sb.append(MathUtils.rounded(getSa())).append('\t');
sb.append(config.getFittingWidth()).append('\t');
sb.append(PsfProtosHelper.getName(fitConfig.getPsfType()));
sb.append(":").append(PeakFit.getSolverName(fitConfig));
if (fitConfig.isModelCameraMle()) {
sb.append(":Camera\t");
// Add details of the noise model for the MLE
final CalibrationReader r = new CalibrationReader(fitConfig.getCalibration());
sb.append("EM=").append(r.isEmCcd());
sb.append(":A=").append(MathUtils.rounded(r.getCountPerElectron()));
sb.append(":N=").append(MathUtils.rounded(r.getReadNoise()));
sb.append('\t');
} else {
sb.append("\t\t");
}
final String analysisPrefix = sb.toString();
// -=-=-=-=-
showResults(results, settings.showResults);
sb.setLength(0);
final int n = countN(results);
// Create the benchmark settings and the fitting settings
sb.append(numberOfMolecules).append('\t');
sb.append(n).append('\t');
sb.append(MathUtils.rounded(density)).append('\t');
sb.append(MathUtils.rounded(simulationParameters.minSignal)).append('\t');
sb.append(MathUtils.rounded(simulationParameters.maxSignal)).append('\t');
sb.append(MathUtils.rounded(simulationParameters.averageSignal)).append('\t');
sb.append(MathUtils.rounded(simulationParameters.sd)).append('\t');
sb.append(MathUtils.rounded(simulationParameters.pixelPitch)).append('\t');
sb.append(MathUtils.rounded(getSa() * simulationParameters.pixelPitch)).append('\t');
sb.append(MathUtils.rounded(simulationParameters.gain)).append('\t');
sb.append(MathUtils.rounded(simulationParameters.readNoise)).append('\t');
sb.append(MathUtils.rounded(simulationParameters.background)).append('\t');
sb.append(MathUtils.rounded(simulationParameters.noise)).append('\t');
sb.append(MathUtils.rounded(simulationParameters.averageSignal / simulationParameters.noise)).append('\t');
sb.append(config.getFittingWidth()).append('\t');
sb.append(PsfProtosHelper.getName(fitConfig.getPsfType()));
sb.append(":").append(PeakFit.getSolverName(fitConfig));
if (fitConfig.isModelCameraMle()) {
sb.append(":Camera\t");
// Add details of the noise model for the MLE
final CalibrationReader r = new CalibrationReader(fitConfig.getCalibration());
sb.append("EM=").append(r.isEmCcd());
sb.append(":A=").append(MathUtils.rounded(r.getCountPerElectron()));
sb.append(":N=").append(MathUtils.rounded(r.getReadNoise()));
sb.append('\t');
} else {
sb.append("\t\t");
}
// Now output the actual results ...
// Show histograms as cumulative to avoid problems with bin width
// Residuals scores
// Iterations and evaluations where fit was OK
final StoredDataStatistics[] stats = new StoredDataStatistics[Settings.NAMES2.length];
for (int i = 0; i < stats.length; i++) {
stats[i] = new StoredDataStatistics();
}
// For Jaccard scoring we need to count the score with no residuals threshold,
// i.e. Accumulate the score accepting all doublets that were fit
double tp = 0;
double fp = 0;
double bestTp = 0;
double bestFp = 0;
final ArrayList<DoubletBonus> data = new ArrayList<>(results.size());
for (final DoubletResult result : results) {
final double score = result.getMaxScore();
// Filter the singles that would be accepted
if (result.good1) {
// Filter the doublets that would be accepted
if (result.good2) {
final double tp2 = result.tp2a + result.tp2b;
final double fp2 = result.fp2a + result.fp2b;
tp += tp2;
fp += fp2;
if (result.tp2a > 0.5) {
bestTp += result.tp2a;
bestFp += result.fp2a;
}
if (result.tp2b > 0.5) {
bestTp += result.tp2b;
bestFp += result.fp2b;
}
// Store this as a doublet bonus
data.add(new DoubletBonus(score, result.getAvScore(), tp2 - result.tp1, fp2 - result.fp1));
} else {
// No doublet fit so this will always be the single fit result
tp += result.tp1;
fp += result.fp1;
if (result.tp1 > 0.5) {
bestTp += result.tp1;
bestFp += result.fp1;
}
}
}
// Build statistics
final int c = result.matchClass;
// True results, i.e. where there was a choice between single or doublet
if (result.valid) {
stats[c].add(score);
}
// Of those where the fit was good, summarise the iterations and evaluations
if (result.good1) {
stats[3].add(result.iter1);
stats[4].add(result.eval1);
// about the iteration increase for singles that are not doublets.
if (c != 0 && result.good2) {
stats[5].add(result.iter2);
stats[6].add(result.eval2);
}
}
}
// Debug the counts
// double tpSingle = 0;
// double fpSingle = 0;
// double tpDoublet = 0;
// double fpDoublet = 0;
// int nSingle = 0, nDoublet = 0;
// for (DoubletResult result : results) {
// if (result.good1) {
// if (result.good2) {
// tpDoublet += result.tp2a + result.tp2b;
// fpDoublet += result.fp2a + result.fp2b;
// nDoublet++;
// }
// tpSingle += result.tp1;
// fpSingle += result.fp1;
// nSingle++;
// }
// }
// System.out.printf("Single %.1f,%.1f (%d) : Doublet %.1f,%.1f (%d)\n", tpSingle, fpSingle,
// nSingle, tpDoublet, fpDoublet, nDoublet * 2);
// Summarise score for true results
final Percentile p = new Percentile(99);
for (int c = 0; c < stats.length; c++) {
final double[] values = stats[c].getValues();
// Sorting is need for the percentile and the cumulative histogram so do it once
Arrays.sort(values);
sb.append(MathUtils.rounded(stats[c].getMean())).append("+/-").append(MathUtils.rounded(stats[c].getStandardDeviation())).append(" (").append(stats[c].getN()).append(") ").append(MathUtils.rounded(p.evaluate(values))).append('\t');
if (settings.showHistograms && settings.displayHistograms[c + Settings.NAMES.length]) {
showCumulativeHistogram(values, Settings.NAMES2[c]);
}
}
sb.append(Settings.MATCHING_METHODS[settings.matchingMethod]).append('\t');
// Plot a graph of the additional results we would fit at all score thresholds.
// This assumes we just pick the the doublet if we fit it (NO FILTERING at all!)
// Initialise the score for residuals 0
// Store this as it serves as a baseline for the filtering analysis
final ResidualsScore residualsScoreMax = computeScores(data, tp, fp, numberOfMolecules, true);
final ResidualsScore residualsScoreAv = computeScores(data, tp, fp, numberOfMolecules, false);
residualsScore = (settings.useMaxResiduals) ? residualsScoreMax : residualsScoreAv;
if (settings.showJaccardPlot) {
plotJaccard(residualsScore, null);
}
final String bestJaccard = MathUtils.rounded(bestTp / (bestFp + numberOfMolecules)) + '\t';
final String analysisPrefix2 = analysisPrefix + bestJaccard;
sb.append(bestJaccard);
addJaccardScores(sb);
sb.append('\t').append(TextUtils.nanosToString(runTime));
createSummaryTable().append(sb.toString());
// Store results in memory for later analysis
referenceResults.set(new ReferenceResults(results, residualsScoreMax, residualsScoreAv, numberOfMolecules, analysisPrefix2));
}
use of uk.ac.sussex.gdsc.core.utils.StoredDataStatistics in project GDSC-SMLM by aherbert.
the class BlinkEstimatorTest method estimateBlinking.
private TIntHashSet estimateBlinking(UniformRandomProvider rg, double blinkingRate, double ton, double toff, int particles, double fixedFraction, boolean timeAtLowerBound, boolean doAssert) {
Assumptions.assumeTrue(TestSettings.allow(TestComplexity.MAXIMUM));
final SpatialIllumination activationIllumination = new UniformIllumination(100);
int totalSteps = 100;
final double eAct = totalSteps * 0.3 * activationIllumination.getAveragePhotons();
final ImageModel imageModel = new ActivationEnergyImageModel(eAct, activationIllumination, ton, 0, toff, 0, blinkingRate, rg);
final double[] max = new double[] { 256, 256, 32 };
final double[] min = new double[3];
final SpatialDistribution distribution = new UniformDistribution(min, max, rg.nextInt());
final List<CompoundMoleculeModel> compounds = new ArrayList<>(1);
final CompoundMoleculeModel c = new CompoundMoleculeModel(1, 0, 0, 0, Arrays.asList(new MoleculeModel(0, 0, 0, 0)));
c.setDiffusionRate(diffusionRate);
c.setDiffusionType(DiffusionType.RANDOM_WALK);
compounds.add(c);
final List<CompoundMoleculeModel> molecules = imageModel.createMolecules(compounds, particles, distribution, false);
// Activate fluorophores
final List<? extends FluorophoreSequenceModel> fluorophores = imageModel.createFluorophores(molecules, totalSteps);
totalSteps = checkTotalSteps(totalSteps, fluorophores);
final List<LocalisationModel> localisations = imageModel.createImage(molecules, fixedFraction, totalSteps, photons, 0.5, false);
// // Remove localisations to simulate missed counts.
// List<LocalisationModel> newLocalisations = new
// ArrayList<LocalisationModel>(localisations.size());
// boolean[] id = new boolean[fluorophores.size() + 1];
// Statistics photonStats = new Statistics();
// for (LocalisationModel l : localisations)
// {
// photonStats.add(l.getIntensity());
// // Remove by intensity threshold and optionally at random.
// if (l.getIntensity() < minPhotons || rand.nextDouble() < pDelete)
// continue;
// newLocalisations.add(l);
// id[l.getId()] = true;
// }
// localisations = newLocalisations;
// logger.info("Photons = %f", photonStats.getMean());
//
// List<FluorophoreSequenceModel> newFluorophores = new
// ArrayList<FluorophoreSequenceModel>(fluorophores.size());
// for (FluorophoreSequenceModel f : fluorophores)
// {
// if (id[f.getId()])
// newFluorophores.add(f);
// }
// fluorophores = newFluorophores;
final MemoryPeakResults results = new MemoryPeakResults();
final CalibrationWriter calibration = new CalibrationWriter();
calibration.setNmPerPixel(pixelPitch);
calibration.setExposureTime(msPerFrame);
calibration.setCountPerPhoton(1);
results.setCalibration(calibration.getCalibration());
results.setPsf(PsfHelper.create(PSFType.ONE_AXIS_GAUSSIAN_2D));
final float b = 0;
float intensity;
final float z = 0;
for (final LocalisationModel l : localisations) {
// Remove by intensity threshold and optionally at random.
if (l.getIntensity() < minPhotons || rg.nextDouble() < probabilityDelete) {
continue;
}
final int frame = l.getTime();
intensity = (float) l.getIntensity();
final float x = (float) l.getX();
final float y = (float) l.getY();
final float[] params = Gaussian2DPeakResultHelper.createParams(b, intensity, x, y, z, psfWidth);
results.add(frame, 0, 0, 0, 0, 0, 0, params, null);
}
// Add random localisations
// Intensity doesn't matter at the moment for tracing
intensity = (float) photons;
for (int i = (int) (localisations.size() * probabilityAdd); i-- > 0; ) {
final int frame = 1 + rg.nextInt(totalSteps);
final float x = (float) (rg.nextDouble() * max[0]);
final float y = (float) (rg.nextDouble() * max[1]);
final float[] params = Gaussian2DPeakResultHelper.createParams(b, intensity, x, y, z, psfWidth);
results.add(frame, 0, 0, 0, 0, 0, 0, params, null);
}
// Get actual simulated stats ...
final Statistics statsNBlinks = new Statistics();
final Statistics statsTOn = new Statistics();
final Statistics statsTOff = new Statistics();
final Statistics statsSampledNBlinks = new Statistics();
final Statistics statsSampledTOn = new Statistics();
final StoredDataStatistics statsSampledTOff = new StoredDataStatistics();
for (final FluorophoreSequenceModel f : fluorophores) {
statsNBlinks.add(f.getNumberOfBlinks());
statsTOn.add(f.getOnTimes());
statsTOff.add(f.getOffTimes());
final int[] on = f.getSampledOnTimes();
statsSampledNBlinks.add(on.length);
statsSampledTOn.add(on);
statsSampledTOff.add(f.getSampledOffTimes());
}
logger.info(FunctionUtils.getSupplier("N = %d (%d), N-blinks = %f, tOn = %f, tOff = %f, Fixed = %f", fluorophores.size(), localisations.size(), blinkingRate, ton, toff, fixedFraction));
logger.info(FunctionUtils.getSupplier("Actual N-blinks = %f (%f), tOn = %f (%f), tOff = %f (%f), 95%% = %f, max = %f", statsNBlinks.getMean(), statsSampledNBlinks.getMean(), statsTOn.getMean(), statsSampledTOn.getMean(), statsTOff.getMean(), statsSampledTOff.getMean(), statsSampledTOff.getStatistics().getPercentile(95), statsSampledTOff.getStatistics().getMax()));
logger.info("-=-=--=-");
final BlinkEstimator be = new BlinkEstimator();
be.setMaxDarkTime((int) (toff * 10));
be.setMsPerFrame(msPerFrame);
be.setRelativeDistance(false);
final double d = ImageModel.getRandomMoveDistance(diffusionRate);
be.setSearchDistance((fixedFraction < 1) ? Math.sqrt(2 * d * d) * 3 : 0);
be.setTimeAtLowerBound(timeAtLowerBound);
// Assertions.assertTrue("Max dark time must exceed the dark time of the data (otherwise no
// plateau)",
// be.maxDarkTime > statsSampledTOff.getStatistics().getMax());
final int nMolecules = fluorophores.size();
if (usePopulationStatistics) {
blinkingRate = statsNBlinks.getMean();
toff = statsTOff.getMean();
} else {
blinkingRate = statsSampledNBlinks.getMean();
toff = statsSampledTOff.getMean();
}
// See if any fitting regime gets a correct answer
final TIntHashSet ok = new TIntHashSet();
for (int numberOfFittedPoints = MIN_FITTED_POINTS; numberOfFittedPoints <= MAX_FITTED_POINTS; numberOfFittedPoints++) {
be.setNumberOfFittedPoints(numberOfFittedPoints);
be.computeBlinkingRate(results, true);
final double moleculesError = DoubleEquality.relativeError(nMolecules, be.getNMolecules());
final double blinksError = DoubleEquality.relativeError(blinkingRate, be.getNBlinks());
final double offError = DoubleEquality.relativeError(toff * msPerFrame, be.getTOff());
logger.info(FunctionUtils.getSupplier("Error %d: N = %f, blinks = %f, tOff = %f : %f", numberOfFittedPoints, moleculesError, blinksError, offError, (moleculesError + blinksError + offError) / 3));
if (moleculesError < relativeError && blinksError < relativeError && offError < relativeError) {
ok.add(numberOfFittedPoints);
logger.info("-=-=--=-");
logger.info(FunctionUtils.getSupplier("*** Correct at %d fitted points ***", numberOfFittedPoints));
if (doAssert) {
break;
}
}
// if (!be.isIncreaseNFittedPoints())
// break;
}
logger.info("-=-=--=-");
if (doAssert) {
Assertions.assertFalse(ok.isEmpty());
}
// relativeError);
return ok;
}
Aggregations