package gdsc.smlm.ij.plugins; import java.util.Iterator; import org.apache.commons.math3.random.RandomGenerator; import org.apache.commons.math3.random.Well19937c; import org.apache.commons.math3.util.CombinatoricsUtils; import org.junit.Assert; import org.junit.Test; /** * Test the PulseActivationAnalysis unmixing functions */ public class PulseActivationAnalysisTest { @Test public void canLinearlyUnmix2Channels() { for (int n = 0; n <= 2; n++) for (int m = 0; m <= 2; m++) canLinearlyUnmix2Channels(n, m); } private void canLinearlyUnmix2Channels(int n, int m) { RandomGenerator r = new Well19937c(30051977); try { for (int loop = 0; loop < 10; loop++) { // A rough mix of each channel double[] d = create(2, r, 1, 100); // Crosstalk should be below 50% double[] c = create(2, r, 0, 0.5); // Enumerate Iterator<int[]> it = CombinatoricsUtils.combinationsIterator(2, n); while (it.hasNext()) { final int[] channels = it.next(); double[] dd = new double[2]; for (int i : channels) dd[i] = d[i]; Iterator<int[]> it2 = CombinatoricsUtils.combinationsIterator(2, m); while (it2.hasNext()) { final int[] crosstalk = it2.next(); double[] cc = new double[2]; for (int i : crosstalk) cc[i] = c[i]; canLinearlyUnmix2Channels(dd[0], dd[1], cc[0], cc[1]); } } } } catch (AssertionError e) { throw new AssertionError(String.format("channels=%d, crosstalk=%d", n, m), e); } } private double[] create(int size, RandomGenerator r, double min, double range) { double[] d = new double[size]; for (int i = 0; i < size; i++) d[i] = min + r.nextDouble() * range; return d; } private void canLinearlyUnmix2Channels(double d1, double d2, double C21, double C12) { // Solving: // D1 = d1 + C21 * d2 // D2 = d2 + C12 * d1 double D1 = d1 + C21 * d2; double D2 = d2 + C12 * d1; double[] d = PulseActivationAnalysis.unmix(D1, D2, C21, C12); //System.out.printf("d1 %f = %f, d2 %f = %f (C=%f,%f)\n", d1, d[0], d2, d[1], C21, C12); Assert.assertEquals("d1", d1, d[0], delta(d1)); Assert.assertEquals("d2", d2, d[1], delta(d2)); } private double delta(double d) { d *= 1e-10; // Set a limit where we are close enough. This mainly applies when d is zero. if (d < 1e-8) return 1e-8; return d; } @Test public void canLinearlyUnmix3Channels() { for (int n = 0; n <= 3; n++) for (int m = 0; m <= 6; m++) canLinearlyUnmix3Channels(n, m); } private void canLinearlyUnmix3Channels(int n, int m) { RandomGenerator r = new Well19937c(30051977); try { for (int loop = 0; loop < 10; loop++) { // A rough mix of each channel double[] d = create(6, r, 100, 100); // Total crosstalk per channel should be below 50% double[] c = create(6, r, 0, 0.25); // Enumerate Iterator<int[]> it = CombinatoricsUtils.combinationsIterator(3, n); while (it.hasNext()) { final int[] channels = it.next(); double[] dd = new double[3]; for (int i : channels) dd[i] = d[i]; Iterator<int[]> it2 = CombinatoricsUtils.combinationsIterator(6, m); while (it2.hasNext()) { final int[] crosstalk = it2.next(); double[] cc = new double[6]; for (int i : crosstalk) cc[i] = c[i]; canLinearlyUnmix3Channels(dd[0], dd[1], dd[2], cc[0], cc[1], cc[2], cc[3], cc[4], cc[5]); } } } } catch (AssertionError e) { throw new AssertionError(String.format("channels=%d, crosstalk=%d", n, m), e); } } public void canLinearlyUnmix3Channels(double d1, double d2, double d3, double C21, double C31, double C12, double C32, double C13, double C23) { // Solving: // D1 = d1 + C21 * d2 + C31 * d3 // D2 = d2 + C12 * d1 + C32 * d3 // D3 = d3 + C13 * d1 + C23 * d2 double D1 = d1 + C21 * d2 + C31 * d3; double D2 = d2 + C12 * d1 + C32 * d3; double D3 = d3 + C13 * d1 + C23 * d2; double[] d = PulseActivationAnalysis.unmix(D1, D2, D3, C21, C31, C12, C32, C13, C23); //System.out.printf("d1 %f = %f, d2 %f = %f, d3 %f = %f (C=%f,%f,%f,%f,%f)\n", d1, d[0], d2, d[1], d3, d[2], C21, C31, C12, C32, C13, C23); Assert.assertEquals("d1", d1, d[0], delta(d1)); Assert.assertEquals("d2", d2, d[1], delta(d2)); Assert.assertEquals("d3", d3, d[2], delta(d3)); } }