/*-
 * #%L
 * Fiji's plugin for colocalization analysis.
 * %%
 * Copyright (C) 2009 - 2017 Fiji developers.
 * %%
 * 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.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public
 * License along with this program.  If not, see
 * <http://www.gnu.org/licenses/gpl-3.0.html>.
 * #L%
 */
package sc.fiji.coloc.tests;

import static org.junit.Assert.assertEquals;

import net.imglib2.RandomAccessibleInterval;
import net.imglib2.TwinCursor;
import net.imglib2.type.logic.BitType;
import net.imglib2.type.numeric.integer.UnsignedByteType;
import net.imglib2.type.numeric.real.FloatType;
import net.imglib2.view.Views;

import org.junit.Test;

import sc.fiji.coloc.algorithms.MissingPreconditionException;
import sc.fiji.coloc.algorithms.PearsonsCorrelation;
import sc.fiji.coloc.algorithms.PearsonsCorrelation.Implementation;
import sc.fiji.coloc.gadgets.MaskFactory;

/**
 * This class contains JUnit 4 test cases for the Pearson's correlation
 * implementation.
 *
 * @author Dan White
 * @author Tom Kazimiers
 */
public class PearsonsCorrelationTest extends ColocalisationTest {

	/**
	 * Tests if the fast implementation of Pearson's correlation with two
	 * zero correlated images produce a Pearson's R value of about zero.
	 */
	@Test
	public void fastPearsonsZeroCorrTest() throws MissingPreconditionException {
		// create a twin value range cursor that iterates over all pixels of the input data
		TwinCursor<UnsignedByteType> cursor = new TwinCursor<UnsignedByteType>(
				zeroCorrelationImageCh1.randomAccess(),
				zeroCorrelationImageCh2.randomAccess(),
				Views.iterable(zeroCorrelationAlwaysTrueMask).localizingCursor());
		// get the Pearson's value
		double pearsonsR = PearsonsCorrelation.fastPearsons(cursor);
		// check Pearsons R is close to zero
		assertEquals(0.0, pearsonsR, 0.05);
	}

	/**
	 * Tests if the fast implementation of Pearson's correlation with two
	 * positive correlated images produce a Pearson's R value of about 0.75.
	 */
	@Test
	public void fastPearsonsPositiveCorrTest() throws MissingPreconditionException {
		// create a twin value range cursor that iterates over all pixels of the input data
		TwinCursor<UnsignedByteType> cursor = new TwinCursor<UnsignedByteType>(
				positiveCorrelationImageCh1.randomAccess(),
				positiveCorrelationImageCh2.randomAccess(),
				Views.iterable(positiveCorrelationAlwaysTrueMask).localizingCursor());
		// get the Pearson's value
		double pearsonsR = PearsonsCorrelation.fastPearsons(cursor);
		// check Pearsons R is close to 0.75
		assertEquals(0.75, pearsonsR, 0.01);
	}

	/**
	 * Tests if the classic implementation of Pearson's correlation with two
	 * zero correlated images produce a Pearson's R value of about zero.
	 */
	@Test
	public void classicPearsonsZeroCorrTest() throws MissingPreconditionException {
		// create a twin value range cursor that iterates over all pixels of the input data
		TwinCursor<UnsignedByteType> cursor = new TwinCursor<UnsignedByteType>(
				zeroCorrelationImageCh1.randomAccess(),
				zeroCorrelationImageCh2.randomAccess(),
				Views.iterable(zeroCorrelationAlwaysTrueMask).localizingCursor());
		// get the Pearson's value
		double pearsonsR = PearsonsCorrelation
			.classicPearsons(cursor, zeroCorrelationImageCh1Mean, zeroCorrelationImageCh2Mean);
		// check Pearsons R is close to zero
		assertEquals(0.0, pearsonsR, 0.05);
	}

	/**
	 * Tests if the classic implementation of Pearson's correlation with two
	 * positive correlated images produce a Pearson's R value of about 0.75.
	 */
	@Test
	public void classicPearsonsPositiveCorrTest() throws MissingPreconditionException {
		// create a twin value range cursor that iterates over all pixels of the input data
		TwinCursor<UnsignedByteType> cursor = new TwinCursor<UnsignedByteType>(
				positiveCorrelationImageCh1.randomAccess(),
				positiveCorrelationImageCh2.randomAccess(),
				Views.iterable(positiveCorrelationAlwaysTrueMask).localizingCursor());
		// get the Pearson's value
		double pearsonsR = PearsonsCorrelation
			.classicPearsons(cursor, positiveCorrelationImageCh1Mean, positiveCorrelationImageCh2Mean);
		// check Pearsons R is close to 0.75
		assertEquals(0.75, pearsonsR, 0.01);
	}

	/**
	 * Tests Pearson's correlation stays close to zero for image pairs with the same mean and spread
	 * of randomized pixel values around that mean.
	 */
	@Test
	public void differentMeansTest() throws MissingPreconditionException {
		final double initialMean = 0.2;
		final double spread = 0.1;
		final double[] sigma = new double[] {3.0, 3.0};

		RandomAccessibleInterval<BitType> mask = MaskFactory.createMask(new long[] {512, 512}, true);

		for (double mean = initialMean; mean < 1; mean += spread) {
			RandomAccessibleInterval<FloatType> ch1 = TestImageAccessor.produceMeanBasedNoiseImage(new FloatType(),
					512, 512, mean, spread, sigma, 0x01234567);
			RandomAccessibleInterval<FloatType> ch2 = TestImageAccessor.produceMeanBasedNoiseImage(new FloatType(),
					512, 512, mean, spread, sigma, 0x98765432);

			// create a twin value range cursor that iterates over all pixels of the input data
			TwinCursor<FloatType> cursor = new TwinCursor<FloatType>(ch1.randomAccess(),
					ch2.randomAccess(), Views.iterable(mask).localizingCursor());
			double resultFast = PearsonsCorrelation.fastPearsons(cursor);
			assertEquals(0.0, resultFast, 0.1);

			/* This test will throw Missing PreconsitionException, as the means are the same
			 * which causes a numerical problem in the classic implementation of Pearson's
			 * double resultClassic = PearsonsCorrelation.classicPearsons(cursor, mean, mean);
			 * assertTrue(Math.abs(resultClassic) < 0.1);
			 */
		}
	}

	/**
	 * The 1993 paper of Manders et. al about colocalization presents an own
	 * method and testing data for it. For that testing data there are
	 * Pearson colocalization numbers, too, and these get tested in this test.
	 * @throws MissingPreconditionException
	 */
	@Test
	public void mandersPaperImagesTest() throws MissingPreconditionException {
		PearsonsCorrelation<UnsignedByteType> pc =
				new PearsonsCorrelation<UnsignedByteType>(Implementation.Classic);
		double r;

		// test A-A combination
		r = pc.calculatePearsons(mandersA, mandersA, mandersAlwaysTrueMask);
		assertEquals(1.0d, r, 0.01);

		// test A-B combination
		r = pc.calculatePearsons(mandersA, mandersB, mandersAlwaysTrueMask);
		assertEquals(0.72d, r, 0.01);

		// test A-C combination
		r = pc.calculatePearsons(mandersA, mandersC, mandersAlwaysTrueMask);
		assertEquals(0.44d, r, 0.01);

		// test A-D combination
		r = pc.calculatePearsons(mandersA, mandersD, mandersAlwaysTrueMask);
		assertEquals(0.16d, r, 0.01);

		// test A-E combination
		r = pc.calculatePearsons(mandersA, mandersE, mandersAlwaysTrueMask);
		assertEquals(-0.12d, r, 0.01);

		// test A-F combination
		r = pc.calculatePearsons(mandersA, mandersF, mandersAlwaysTrueMask);
		assertEquals(0.22d, r, 0.01);

		// test A-G combination
		r = pc.calculatePearsons(mandersA, mandersG, mandersAlwaysTrueMask);
		assertEquals(0.30d, r, 0.01);

		// test A-H combination
		r = pc.calculatePearsons(mandersA, mandersH, mandersAlwaysTrueMask);
		assertEquals(0.48d, r, 0.01);

		// test A-I combination
		r = pc.calculatePearsons(mandersA, mandersI, mandersAlwaysTrueMask);
		assertEquals(0.23d, r, 0.01);
	}
}