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 java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * Contains a model for a blinking fluorophore. * <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 abstract class FluorophoreSequenceModel extends MoleculeModel implements Comparable<FluorophoreSequenceModel> { public FluorophoreSequenceModel(int id, double x, double y, double z) { super(id, x, y, z); } public FluorophoreSequenceModel(int id, double[] xyz) { super(id, xyz); } /** * The number of times the molecule went into the dark state */ private int blinks = 0; /** * A sequence of fluorescent bursts in pairs of {on,off} times. The burst sequence will be length = 2 * (blinks+1) */ private double[] burstSequence = new double[] { 0, 0 }; protected void setBurstSequence(double[] sequence) { if (sequence != null && sequence.length > 1) { blinks = (sequence.length / 2) - 1; // Ensure the sequence array is an even number in length final int length = 2 * (blinks + 1); if (sequence.length == length) burstSequence = sequence; else burstSequence = Arrays.copyOf(sequence, length); } } /** * @return The number of times the fluorophore blinked */ public int getNumberOfBlinks() { return blinks; } /** * Get the start time, i.e. when the molecule activated. * <p> * Note that a molecule will always have a start time even if it has no blinks. This models a molecule that turns on * and then bleaches immediately. * * @return The start time */ public double getStartTime() { return burstSequence[0]; } /** * Get the end time, i.e. when the molecule bleached. * * @return The end time */ public double getEndTime() { return burstSequence[burstSequence.length - 1]; } /** * @return Fluorescent bursts arranged as list of on/off times: {onT,offT} */ public List<double[]> getBurstSequence() { ArrayList<double[]> data = new ArrayList<double[]>(blinks + 1); for (int i = 0; i <= blinks; i++) { data.add(new double[] { burstSequence[i * 2], burstSequence[i * 2 + 1] }); } return data; } /** * @return Fluorescent bursts arranged as list of on/off times in integer sampling intervals: {onT,offT} */ public List<int[]> getSampledBurstSequence() { ArrayList<int[]> data = new ArrayList<int[]>(blinks + 1); for (int i = 0; i <= blinks; i++) { data.add(new int[] { (int) (burstSequence[i * 2]), (int) (burstSequence[i * 2 + 1]) }); } return data; } /** * Order by time ascending * * @param o * The other fluorophore * @return -1,0,1 * @see java.lang.Comparable#compareTo(java.lang.Object) */ public int compareTo(FluorophoreSequenceModel o) { final double s1 = getStartTime(); final double s2 = o.getStartTime(); return (s1 < s2) ? -1 : (s1 == s2) ? 0 : 1; } /** * @return The duration of the on times */ public double[] getOnTimes() { double[] onTimes = new double[blinks + 1]; for (int i = 0; i <= blinks; i++) { onTimes[i] = burstSequence[i * 2 + 1] - burstSequence[i * 2]; } return onTimes; } /** * @return The duration of the off times */ public double[] getOffTimes() { if (blinks < 1) return new double[0]; double[] offTimes = new double[blinks]; for (int i = 1; i <= blinks; i++) { offTimes[i - 1] = burstSequence[i * 2] - burstSequence[i * 2 - 1]; } return offTimes; } /** * @return The duration of the on times if sampled at integer time intervals */ public int[] getSampledOnTimes() { if (blinks == 0) return new int[] { end(burstSequence[1]) - start(burstSequence[0]) }; // Process all blinks. Join together blinks with an off-time that would not be noticed, // i.e. where the molecule was on in consecutive frames. int[] onTimes = new int[blinks + 1]; int n = 0; int tStart = (int) burstSequence[0]; for (int i = 0; i < blinks; i++) { int end1 = end(burstSequence[i * 2 + 1]); int start2 = start(burstSequence[(i + 1) * 2]); if (start2 - end1 > 0) { onTimes[n++] = end1 - tStart; tStart = start2; } } onTimes[n++] = end(getEndTime()) - tStart; return Arrays.copyOf(onTimes, n); } private int start(double t) { return (int) t; } private int end(double t) { return (int) (Math.ceil(t)); } /** * @return The duration of the off times if sampled at integer time intervals */ public int[] getSampledOffTimes() { if (blinks == 0) return new int[0]; // Process all blinks. Join together blinks with an off-time that would not be noticed, // i.e. where the molecule was on in consecutive frames. int[] offTimes = new int[blinks]; int n = 0; for (int i = 0; i < blinks; i++) { int end1 = end(burstSequence[i * 2 + 1]); int start2 = start(burstSequence[(i + 1) * 2]); if (start2 - end1 > 0) { offTimes[n++] = start2 - end1; } } return Arrays.copyOf(offTimes, n); } /** * @return An array of frames when the molecule was on */ public int[] getOnFrames() { int sequenceStartT = (int) getStartTime(); int sequenceEndT = (int) getEndTime(); int n = 0; int[] onFrames = new int[sequenceEndT - sequenceStartT + 1]; for (int i = 0; i <= blinks; i++) { int on = (int) (burstSequence[i * 2]); int off = (int) (burstSequence[i * 2 + 1]); for (int t = on; t <= off; t++) { onFrames[n++] = t; } } return Arrays.copyOf(onFrames, n); } /** * Scale the times using the specified factor. Allows adjusting the relative time of the sequence. * * @param scale */ public void adjustTime(double scale) { if (scale < 0) throw new IllegalArgumentException("Scale factor must be above zero"); for (int i = 0; i < burstSequence.length; i++) burstSequence[i] *= scale; } }