/*-
 * #%L
 * Mathematical morphology library and plugins for ImageJ/Fiji.
 * %%
 * Copyright (C) 2014 - 2017 INRA.
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser 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 Lesser Public License for more details.
 * 
 * You should have received a copy of the GNU General Lesser Public
 * License along with this program.  If not, see
 * <http://www.gnu.org/licenses/lgpl-3.0.html>.
 * #L%
 */
package inra.ijpb.label;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;

import java.awt.Color;

import org.junit.Assert;
import org.junit.Test;

import ij.IJ;
import ij.ImagePlus;
import ij.ImageStack;
import ij.process.ByteProcessor;
import ij.process.ColorProcessor;
import ij.process.ImageProcessor;
import inra.ijpb.util.ColorMaps.CommonLabelMaps;

public class LabelImagesTest
{
	@Test
	public final void testLabelToRGB_ImageProcessorByteArrayColor()
	{
		// create a byte processor containing four labels
		ImageProcessor image = new ByteProcessor(10, 10);
		for (int y = 0; y < 3; y++)
		{
			for (int x = 0; x < 3; x++)
			{
				image.set(x + 1, y + 1, 1);
				image.set(x + 5, y + 1, 2);
				image.set(x + 1, y + 5, 3);
				image.set(x + 5, y + 5, 4);
			}
		}
		
		// create LUT and background color
		byte[][] lut = CommonLabelMaps.GOLDEN_ANGLE.computeLut(4, false);
		Color bgColor = Color.WHITE;
		
		// compute color image from labels
		ColorProcessor colorImage = LabelImages.labelToRgb(image, lut, bgColor);

		Assert.assertNotEquals(0, colorImage.get(2, 2));
		Assert.assertNotEquals(0, colorImage.get(6, 2));
		Assert.assertNotEquals(0, colorImage.get(2, 6));
		Assert.assertNotEquals(0, colorImage.get(6, 6));
	}

	@Test
	public final void testFindAllLabels_ByteProcessor()
	{
		String fileName = getClass().getResource("/files/blobs-lbl.tif").getFile();
		ImagePlus imagePlus = IJ.openImage(fileName);
		ImageProcessor image = imagePlus.getProcessor();

		int[] labels = LabelImages.findAllLabels(image);
		for (int i = 0; i < labels.length; i++)
		{
			assertFalse(labels[i] == 0);
			assertEquals(i + 1, labels[i]);
		}
	}

	/**
	 * We want labels in increasing order, whatever the order they are detected
	 * in the image.
	 */
	@Test
	public final void testFindAllLabels_Byte_IncreasingOrder()
	{
		ByteProcessor image = new ByteProcessor(6, 6);
		image.set(1, 1, 8);
		image.set(3, 1, 6);
		image.set(4, 1, 6);
		
		image.set(1, 3, 4);
		image.set(3, 3, 2);
		image.set(3, 3, 2);
		image.set(1, 4, 4);
		image.set(3, 4, 2);
		image.set(4, 4, 2);

		int[] labels = LabelImages.findAllLabels(image);
		int[] expectedLabels = new int[]{2, 4, 6, 8};
		for (int i = 0; i < labels.length; i++)
		{
			assertFalse(labels[i] == 0);
			assertEquals(expectedLabels[i], labels[i]);
		}
	}

	@Test
	public final void testFindAllLabels_FloatProcessor()
	{
		String fileName = getClass().getResource("/files/blobs-lbl32.tif").getFile();
		ImagePlus imagePlus = IJ.openImage(fileName);
		ImageProcessor image = imagePlus.getProcessor();

		int[] labels = LabelImages.findAllLabels(image);
		for (int i = 0; i < labels.length; i++)
		{
			assertFalse(labels[i] == 0);
			assertEquals(i + 1, labels[i]);
		}
	}

	@Test
	public final void testKeepLargestLabelImageProcessor()
	{
		String fileName = getClass().getResource("/files/blobs-lbl.tif").getFile();
		ImagePlus imagePlus = IJ.openImage(fileName);
		ImageProcessor image = imagePlus.getProcessor();

		ImageProcessor largestLabel = LabelImages.keepLargestLabel(image);
		
		// background pixel should remain unchanged
		assertEquals(0, largestLabel.get(0, 0));
		// small labels should disappear
		assertEquals(0, largestLabel.get(204, 204));
		// largest label should be set to true
		assertEquals(255, largestLabel.get(90, 160));
	}


	/**
	 * Should thrown an exception
	 */
	@Test(expected=RuntimeException.class)
	public final void testKeepLargestLabelImageProcessor_EmptyImage()
	{
		ImageProcessor image = new ByteProcessor(100, 100);
		@SuppressWarnings("unused")
		ImageProcessor largestLabel = LabelImages.keepLargestLabel(image);
	}

	/**
	 * Should thrown an exception
	 */
	@Test(expected=RuntimeException.class)
	public final void testKeepLargestLabelImageStack_EmptyImage()
	{
		ImageStack image = ImageStack.create(20, 20, 20, 8);
		@SuppressWarnings("unused")
		ImageStack largestLabel = LabelImages.keepLargestLabel(image);
	}

	@Test
	public final void testRemoveLargestLabelImageProcessor()
	{
		String fileName = getClass().getResource("/files/blobs-lbl.tif").getFile();
		ImagePlus imagePlus = IJ.openImage(fileName);
		ImageProcessor image = imagePlus.getProcessor();

		LabelImages.removeLargestLabel(image);
		// background pixel should remain unchanged
		assertEquals(0, image.get(0, 0));
		// small labels should remain unchanged
		assertEquals(50, image.get(204, 204));
		// largest label should disappear
		assertEquals(0, image.get(90, 160));
	}

	@Test
	public final void testAreaOpeningImageProcessor()
	{
		// Create input image: four regions, with sizes 1, 5, 5, and 25
		ByteProcessor image = new ByteProcessor(10, 10);
		image.set(1, 1, 1);
		for (int i = 3; i < 8; i++)
		{
			image.set(i, 1, 2);
			image.set(1, i, 3);
		}
		for (int y = 3; y < 8; y++)
		{
			for (int x = 3; x < 8; x++)
			{
				image.set(x, y, 4);
			}
		}

		// Remove only the first region
		ImageProcessor sizeOpen3 = LabelImages.areaOpening(image, 3);
		assertEquals(0, sizeOpen3.get(1, 1));
		assertEquals(2, sizeOpen3.get(5, 1));
		assertEquals(3, sizeOpen3.get(1, 5));
		assertEquals(4, sizeOpen3.get(5, 5));

		// Remove the first 3 region
		ImageProcessor sizeOpen10 = LabelImages.areaOpening(image, 10);
		assertEquals(0, sizeOpen10.get(1, 1));
		assertEquals(0, sizeOpen10.get(1, 5));
		assertEquals(0, sizeOpen10.get(5, 1));
		assertEquals(4, sizeOpen10.get(5, 5));
	}

	
    @Test
    public final void testMergeLabelsWithGap_ImageProcessor()
    {
        int[][] data = new int[][] {
            {2, 2, 2, 2, 0, 3, 3, 3, 3}, 
            {2, 2, 2, 2, 0, 3, 3, 3, 3}, 
            {2, 2, 2, 0, 6, 0, 3, 3, 3}, 
            {2, 2, 0, 6, 6, 6, 0, 3, 3}, 
            {0, 0, 6, 6, 6, 6, 6, 0, 0}, 
            {5, 5, 0, 6, 6, 6, 0, 8, 8}, 
            {5, 5, 5, 0, 6, 0, 8, 8, 8}, 
            {5, 5, 5, 5, 0, 8, 8, 8, 8}, 
            {5, 5, 5, 5, 0, 8, 8, 8, 8}, 
        };
        
        ImageProcessor labelImage = new ByteProcessor(9, 9);
        for (int y = 0; y < 9; y++)
        {
            for (int x = 0; x < 9; x++)
            {
                labelImage.set(x, y, data[y][x]);
            }
        }
        
        float[] labels = new float[] {3.0f, 5.0f, 6.0f};
        float refLabel = 3.0f;
        LabelImages.mergeLabelsWithGap(labelImage, labels, refLabel, 4);

        assertEquals(2, labelImage.get(0, 0));
        assertEquals(8, labelImage.get(8, 8));

        assertEquals(3, labelImage.get(8, 0));
        assertEquals(3, labelImage.get(4, 4));
        assertEquals(3, labelImage.get(0, 8));

        assertEquals(3, labelImage.get(5, 2));
        assertEquals(3, labelImage.get(2, 5));
    }

    @Test
    public final void testMergeLabelsWithGap_3D_cubic()
    {
        ImageStack image = ImageStack.create(9, 9, 9, 8);
        for (int z = 0; z < 4; z++)
        {
            for (int y = 0; y < 4; y++)
            {
                for (int x = 0; x < 4; x++)
                {
                    image.setVoxel(x, y, z, 2);
                    image.setVoxel(x + 5, y, z, 3);
                    image.setVoxel(x, y + 5, z, 5);
                    image.setVoxel(x + 5, y + 5, z, 7);
                    image.setVoxel(x, y, z + 5, 8);
                    image.setVoxel(x + 5, y, z + 5, 10);
                    image.setVoxel(x, y + 5, z + 5, 11);
                    image.setVoxel(x + 5, y + 5, z + 5, 12);
                }
            }
        }

        float[] labels = new float[] {3.0f, 5.0f, 7.0f};
        float refLabel = 3.0f;
        LabelImages.mergeLabelsWithGap(image, labels, refLabel, 6);

        assertEquals(2, image.getVoxel(0, 0, 0), .01);
        assertEquals(12, image.getVoxel(8, 8, 8), .01);

        assertEquals(3, image.getVoxel(7, 0, 0), .01);
        assertEquals(3, image.getVoxel(0, 7, 0), .01);
        assertEquals(3, image.getVoxel(7, 7, 0), .01);

        // should remove boundary between labels 2-3 and 3-5
        assertEquals(3, image.getVoxel(7, 5, 0), .01);
        assertEquals(3, image.getVoxel(5, 7, 0), .01);

        // should keep boundary with region with label 2
        assertEquals(0, image.getVoxel(4, 0, 0), .01);
        assertEquals(0, image.getVoxel(0, 4, 0), .01);
    }

}