package gdsc.smlm.model; /*----------------------------------------------------------------------------- * 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 org.apache.commons.math3.random.RandomDataGenerator; import gnu.trove.list.array.TDoubleArrayList; /** * Contains a continuous-time model for a blinking fluorophore. Assumes a constant activation laser and a simple * exponential model for the average activation time. * <p> * Based on the work of Coltharp et al (2012) Accurate Construction of photoactivated localization microscopy images for * quantitative measurements. PLOS One 7, Issue 12, pp 1-15 */ public class StandardFluorophoreSequenceModel extends FluorophoreSequenceModel { /** * Construct a new flourophore * * @param tAct * Average time for activation * @param id * The identifier * @param xyz * The [x,y,z] coordinates * @param tOn * Average on-state time * @param tOff * Average off-state time for the first dark state * @param tOff * Average off-state time for the second dark state * @param nBlinks * Average number of blinks int the first dark state (used for each burst between second dark states) * @param nBlinks2 * Average number of blinks into the second dark state */ public StandardFluorophoreSequenceModel(double tAct, int id, double[] xyz, double tOn, double tOff, double tOff2, double nBlinks, double nBlinks2, boolean useGeometricBlinkingDistribution) { this(tAct, id, xyz, tOn, tOff, tOff2, nBlinks, nBlinks2, useGeometricBlinkingDistribution, new RandomDataGenerator()); } /** * Construct a new flourophore * * @param tAct * Average time for activation * @param id * The identifier * @param xyz * The [x,y,z] coordinates * @param tOn * Average on-state time * @param tOff * Average off-state time for the first dark state * @param tOff * Average off-state time for the second dark state * @param nBlinks * Average number of blinks int the first dark state (used for each burst between second dark states) * @param nBlinks2 * Average number of blinks into the second dark state * @param randomGenerator */ public StandardFluorophoreSequenceModel(double tAct, int id, double[] xyz, double tOn, double tOff, double tOff2, double nBlinks, double nBlinks2, boolean useGeometricBlinkingDistribution, RandomDataGenerator randomGenerator) { super(id, xyz); init(randomGenerator.nextExponential(tAct), tOn, tOff, tOff2, nBlinks, nBlinks2, useGeometricBlinkingDistribution, randomGenerator); } /** * Construct a new flourophore * * @param id * The identifier * @param xyz * The [x,y,z] coordinates * @param startT * The activation time * @param tOn * Average on-state time * @param tOff * Average off-state time for the first dark state * @param tOff * Average off-state time for the second dark state * @param nBlinks * Average number of blinks int the first dark state (used for each burst between second dark states) * @param nBlinks2 * Average number of blinks into the second dark state */ public StandardFluorophoreSequenceModel(int id, double[] xyz, double startT, double tOn, double tOff, double tOff2, double nBlinks, double nBlinks2, boolean useGeometricBlinkingDistribution) { super(id, xyz); init(startT, tOn, tOff, tOff2, nBlinks, nBlinks2, useGeometricBlinkingDistribution, new RandomDataGenerator()); } /** * Construct a new flourophore * * @param id * The identifier * @param xyz * The [x,y,z] coordinates * @param startT * The activation time * @param tOn * Average on-state time * @param tOff * Average off-state time for the first dark state * @param tOff * Average off-state time for the second dark state * @param nBlinks * Average number of blinks int the first dark state (used for each burst between second dark states) * @param nBlinks2 * Average number of blinks into the second dark state * @param randomGenerator */ public StandardFluorophoreSequenceModel(int id, double[] xyz, double startT, double tOn, double tOff, double tOff2, double nBlinks, double nBlinks2, boolean useGeometricBlinkingDistribution, RandomDataGenerator randomGenerator) { super(id, xyz); init(startT, tOn, tOff, tOff2, nBlinks, nBlinks2, useGeometricBlinkingDistribution, randomGenerator); } private void init(double t, double tOn, double tOff, double tOff2, double nBlinks, double nBlinks2, boolean useGeometricBlinkingDistribution, RandomDataGenerator rand) { // Model two dark states: short and long. The second tOff and nBlinks is for the long dark state: // // ++-+-+++-+.................+-+--++-+................................+--+++-+ // // + = on // - = Short dark state // . = Long dark state // Note: 1+nBlinks is the number of on-states TDoubleArrayList sequence = new TDoubleArrayList(); // Perform a set number of long blinks int nLongBlinks = getBlinks(useGeometricBlinkingDistribution, rand, nBlinks2); for (int n = 0; n <= nLongBlinks; n++) { // For each burst between long blinks perform a number of short blinks int nShortBlinks = getBlinks(useGeometricBlinkingDistribution, rand, nBlinks); // Starts on the current time sequence.add(t); // Stops after the on-time t += rand.nextExponential(tOn); sequence.add(t); // Remaining bursts for (int i = 0; i < nShortBlinks; i++) { // Next burst starts after the short off-time t += rand.nextExponential(tOff); sequence.add(t); // Stops after the on-time t += rand.nextExponential(tOn); sequence.add(t); } // Add the long dark state if there are more bursts. t += rand.nextExponential(tOff2); } // Convert the sequence to the burst sequence array setBurstSequence(sequence.toArray()); } /** * Get the number of blinks using the specified random data generator using a Poisson or Geometric distribution. * @param useGeometricBlinkingDistribution * @param rand * @param mean * @return The number of blinks */ public static int getBlinks(boolean useGeometricBlinkingDistribution, RandomDataGenerator rand, double mean) { if (mean > 0) { return (useGeometricBlinkingDistribution) ? nextGeometric(rand, mean) : (int) rand.nextPoisson(mean); } return 0; } private static int nextGeometric(RandomDataGenerator rand, double mean) { // Use a geometric distribution by sampling the floor from the exponential. // Geometric distribution where k { 0, 1, 2, ... } // See: http://en.wikipedia.org/wiki/Geometric_distribution#Related_distributions final double p = 1 / (1 + mean); return (int) Math.floor(Math.log(rand.nextUniform(0, 1, true)) / Math.log(1 - p)); } }